# app.py import gradio as gr import pillow_heif import spaces import torch from PIL import Image, ImageEnhance, ImageFilter # ImageFilter 추가 from refiners.fluxion.utils import manual_seed, no_grad from utils import load_ic_light, resize_modulo_8 import zipfile from io import BytesIO import tempfile import cv2 # OpenCV 추가 import numpy as np # NumPy 추가 # HEIF/AVIF 이미지 지원 등록 pillow_heif.register_heif_opener() pillow_heif.register_avif_opener() # GPU 설정 DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") DTYPE = torch.float16 if torch.cuda.is_available() and torch.cuda.is_bf16_supported() else torch.float32 ic_light = load_ic_light(device=DEVICE, dtype=DTYPE) # 모델을 지정된 디바이스로 이동 ic_light.to(device=DEVICE, dtype=DTYPE) ic_light.device = DEVICE ic_light.dtype = DTYPE ic_light.solver = ic_light.solver.to(device=DEVICE, dtype=DTYPE) # 번역 관련 라이브러리 및 설정 import os from openai import OpenAI import uuid import time import logging import random from datetime import datetime # 로깅 설정 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(message)s' ) logger = logging.getLogger(__name__) # API 키 설정 OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") # API 클라이언트 설정 openai_client = OpenAI(api_key=OPENAI_API_KEY) def validate_input(text: str) -> bool: """입력 텍스트 유효성 검사""" if not text.strip(): return False if not any('\u3131' <= char <= '\u318F' or '\uAC00' <= char <= '\uD7A3' for char in text): return False return True def translate_to_english(input_text: str): """한국어를 영어로 번역하는 함수 (GPT-4-mini 사용)""" logger.info("GPT-4-mini 번역 시작") if not validate_input(input_text): logger.info("유효하지 않은 입력입니다.") return "유효하지 않은 입력입니다. 한글을 포함한 텍스트를 입력해주세요." try: # 현재 시간을 기반으로 랜덤 시드 설정 current_time = int(time.time() * 1000) random.seed(current_time) # 온도(temperature)와 top_p 값을 매번 랜덤하게 설정 temperature = random.uniform(0.4, 0.85) top_p = random.uniform(0.9, 0.98) request_id = str(uuid.uuid4())[:8] timestamp_micro = int(time.time() * 1000000) % 1000 system_msg = f"REQ-{request_id}-{timestamp_micro}" # 시간에 따라 변화하는 프롬프트 수식어 추가 current_hour = datetime.now().hour time_context = f"Consider the current time is {current_hour}:00. " response = openai_client.chat.completions.create( model="gpt-4o-mini", messages=[ { "role": "system", "content": system_msg }, { "role": "user", "content": f""" 1. Translate the input Korean text into English with these rules: 2. Output ONLY the English translation. 3. Create a coherent, brief scene using provided words/phrases. 4. Stay strictly within the scope of input words. 5. Maintain flow and natural transitions. 6. Keep scene descriptions concise but complete. 7. Do not add external elements. 8. Focus on core scene elements. 9. Preserve original meaning and intent. 10. No explanations or meta-commentary. 11. No formatting beyond basic text. 12. Just the direct English translation. Additional guidance: 13. Use input words harmoniously. 14. Create clear mental imagery. 15. Keep scene contained and focused. 16. Ensure logical connections. 17. Maintain narrative flow. Input: {input_text} [Seed: {current_time}] """ } ], max_tokens=100, temperature=temperature, top_p=top_p, seed=current_time ) translated = response.choices[0].message.content.strip() logger.info("GPT-4-mini 번역 완료") return translated except Exception as e: logger.error(f"번역 중 오류 발생: {str(e)}") return f"번역 중 오류가 발생했습니다: {str(e)}" @spaces.GPU(duration=120) # GPU 사용을 명시하는 데코레이터 @no_grad() def generate_images( image: Image.Image, prompt: str, ) -> tuple[Image.Image, Image.Image, Image.Image, Image.Image]: assert image.mode == "RGBA", "입력 이미지는 RGBA 모드여야 합니다." # 시드 랜덤 설정 seed = torch.seed() # 랜덤 시드 생성 manual_seed(seed) # 네거티브 프롬프트 고정 negative_prompt = "dirty, messy, worst quality, low quality, watermark, signature, jpeg artifacts, deformed, monochrome, black and white" # 기본값 설정 condition_scale = 1.25 num_inference_steps = 25 strength_first_pass = 0.9 strength_second_pass = 0.5 # 이미지 크기 조정 image = resize_modulo_8(image, 768) # RGB와 알파 채널 분리 mask = image.getchannel("A") image_rgb = image.convert("RGB") # 텍스트 임베딩 계산 clip_text_embedding = ic_light.compute_clip_text_embedding(text=prompt, negative_text=negative_prompt) ic_light.set_ic_light_condition(image=image_rgb, mask=mask) # 조명 선호도 기본값 설정 light_pref_image = None # "None"으로 고정 if light_pref_image is None: x = torch.randn_like(ic_light._ic_light_condition) # pyright: ignore[reportPrivateUsage] strength_first_pass = 1.0 else: x = ic_light.lda.image_to_latents(light_pref_image) x = ic_light.solver.add_noise(x, noise=torch.randn_like(x), step=0) # 첫 번째 패스 설정 num_steps = int(round(num_inference_steps / strength_first_pass)) first_step = int(num_steps * (1 - strength_first_pass)) ic_light.set_inference_steps(num_steps, first_step) # 첫 번째 패스 for step in ic_light.steps: x = ic_light( x, step=step, clip_text_embedding=clip_text_embedding, condition_scale=condition_scale, ) # 두 번째 패스 설정 num_steps = int(round(num_inference_steps / strength_second_pass)) first_step = int(num_steps * (1 - strength_second_pass)) ic_light.set_inference_steps(num_steps, first_step) # 초기화된 라텐트 x = ic_light.solver.add_noise(x, noise=torch.randn_like(x), step=first_step) # 두 번째 패스 for step in ic_light.steps: x = ic_light( x, step=step, clip_text_embedding=clip_text_embedding, condition_scale=condition_scale, ) # 배경 이미지 생성 bg_image = ic_light.lda.latents_to_image(x) # 최종 이미지 합성 (전경은 조정 없이 합성) result = Image.composite(image_rgb, bg_image, mask) return image_rgb, bg_image, mask, result # image_rgb 추가 @no_grad() def adjust_final_image( bg_image: Image.Image, mask: Image.Image, bg_brightness: float, bg_contrast: float, bg_saturation: float, bg_temperature: float, # 색온도 파라미터 bg_vibrance: float, bg_color_mixer_blues: float, bg_shadows: float, # Shadows 파라미터 ) -> Image.Image: """배경 이미지를 조정하는 함수""" print("Adjusting Final Image with the following parameters:") print(f"Background - Brightness: {bg_brightness}, Contrast: {bg_contrast}, " f"Saturation: {bg_saturation}, Temperature: {bg_temperature}, " f"Vibrance: {bg_vibrance}, Blues: {bg_color_mixer_blues}, Shadows: {bg_shadows}") # 밝기 조정 enhancer = ImageEnhance.Brightness(bg_image) bg_image = enhancer.enhance(bg_brightness) # 대비 조정 enhancer = ImageEnhance.Contrast(bg_image) bg_image = enhancer.enhance(bg_contrast) # 채도 조정 enhancer = ImageEnhance.Color(bg_image) bg_image = enhancer.enhance(bg_saturation) # 색온도 조정 if bg_temperature != 0.0: # PIL 이미지를 OpenCV 형식으로 변환 cv_image = cv2.cvtColor(np.array(bg_image), cv2.COLOR_RGB2BGR).astype(np.float32) # 색온도 조정 로직 value = float(bg_temperature) * 30 # 색온도 효과 스케일 조절 if value > 0: # 따뜻하게 cv_image[:, :, 2] = cv_image[:, :, 2] + value # Red 채널 증가 cv_image[:, :, 0] = cv_image[:, :, 0] - value # Blue 채널 감소 else: # 차갑게 cv_image[:, :, 2] = cv_image[:, :, 2] + value # Red 채널 감소 cv_image[:, :, 0] = cv_image[:, :, 0] - value # Blue 채널 증가 # 채널 값 클리핑 cv_image[:, :, 2] = np.clip(cv_image[:, :, 2], 0, 255) cv_image[:, :, 0] = np.clip(cv_image[:, :, 0], 0, 255) # 다시 PIL 이미지로 변환 bg_image = Image.fromarray(cv2.cvtColor(cv_image.astype(np.uint8), cv2.COLOR_BGR2RGB)) # 활기(Vibrance) 조정: 채도를 비례적으로 증가/감소 if bg_vibrance != 0.0: # Vibrance는 일반적인 채도보다 덜 민감하게 채도를 조절 converter = ImageEnhance.Color(bg_image) factor = 1 + (bg_vibrance / 100.0) bg_image = converter.enhance(factor) # 컬러 믹서 블루 조정: 파랑 채널을 개별적으로 조정 if bg_color_mixer_blues != 0.0: r, g, b = bg_image.split() b = b.point(lambda p: min(255, max(0, p + bg_color_mixer_blues))) bg_image = Image.merge("RGB", (r, g, b)) # Shadows 조정: 어두운 영역을 조정하고 가장자리를 부드럽게 처리 if bg_shadows != 0.0: # 어두운 영역을 식별하기 위해 그레이스케일 변환 grayscale = bg_image.convert("L") # 어두운 영역 마스크 생성 (임계값 128 이하) mask_dark = grayscale.point(lambda p: p < 128 and 255) mask_dark = mask_dark.convert("L") # 그레이스케일로 변환 # 마스크 가장자리 부드럽게 처리 (Gaussian Blur 적용) mask_dark = mask_dark.filter(ImageFilter.GaussianBlur(radius=10)) # 블러 반경 조절 가능 # 그림자 조정을 위한 밝기 조정 if bg_shadows < 0: # 어두운 영역을 더 어둡게 factor = 1 + (bg_shadows / 100.0) # 예: -50 -> 0.5 enhancer = ImageEnhance.Brightness(bg_image) dark_adjusted = enhancer.enhance(factor) else: # 어두운 영역을 밝게 factor = 1 + (bg_shadows / 100.0) # 예: 50 -> 1.5 enhancer = ImageEnhance.Brightness(bg_image) dark_adjusted = enhancer.enhance(factor) # 마스크를 사용하여 어두운 영역만 조정된 이미지와 합성 bg_image = Image.composite(dark_adjusted, bg_image, mask_dark) # 조정된 배경 반환 return bg_image def download_all(original_image, bg_image, final_image): """ 모든 이미지를 ZIP 파일으로 압축하여 다운로드할 수 있도록 합니다. """ if original_image is None or bg_image is None or final_image is None: print("Download function received None input.") return None print("Preparing images for download...") # Convert images to RGB if they are not if original_image.mode != "RGB": original_image = original_image.convert("RGB") if bg_image.mode != "RGB": bg_image = bg_image.convert("RGB") if final_image.mode != "RGB": final_image = final_image.convert("RGB") # ZIP 파일 생성 with tempfile.NamedTemporaryFile(delete=False, suffix=".zip") as tmp_file: with zipfile.ZipFile(tmp_file, 'w') as zip_file: # 원본 이미지 저장 original_buffer = BytesIO() original_image.save(original_buffer, format="JPEG") zip_file.writestr("original_image.jpg", original_buffer.getvalue()) # 생성된 배경 이미지 저장 bg_buffer = BytesIO() bg_image.save(bg_buffer, format="JPEG") zip_file.writestr("background_image.jpg", bg_buffer.getvalue()) # 최종 결과 이미지 저장 final_buffer = BytesIO() final_image.save(final_buffer, format="JPEG") zip_file.writestr("final_image.jpg", final_buffer.getvalue()) zip_path = tmp_file.name print(f"ZIP file created at {zip_path}") return zip_path def reset_sliders(initial_result): """ 슬라이더를 "필터없음"의 초기값으로 초기화하고 최종 결과 이미지를 초기 생성된 이미지로 복원하는 함수. """ print("Resetting sliders to default values.") return ( "필터없음", # filter_radio gr.update(value=1.0), # 밝기 gr.update(value=1.0), # 대비 gr.update(value=1.0), # 채도 gr.update(value=0.0), # 색온도 gr.update(value=0.0), # 활기 gr.update(value=0.0), # 컬러 믹서 (블루) gr.update(value=0.0), # 그림자 initial_result, # 필터 적용 이미지 ) # Gradio 인터페이스 설정 def create_interface(): css = """ /* 프롬프트 텍스트박스 라벨과 입력 텍스트만 검은색으로 설정 */ .gr-textbox > label { color: #000 !important; } .gr-textbox input { color: #000 !important; } /* 필터 선택 라디오 버튼 라벨만 검은색으로 설정 */ .gradio-radio > label { color: #000 !important; } /* 슬라이더 라벨과 현재 값 텍스트만 검은색으로 설정 */ .gr-slider > label { color: #000 !important; } .gr-slider .slider-value { color: #000 !important; } /* 선택된 라디오 버튼 텍스트를 검은색으로 유지하고 배경색을 흰색으로 설정 */ .gradio-radio label[aria-checked="true"] { color: #000 !important; background-color: #fff !important; /* 흰색 배경 */ padding: 4px 8px; border-radius: 4px; border: 1px solid #000 !important; /* 검은색 테두리 추가 */ } /* 선택되지 않은 라디오 버튼 텍스트는 검은색 유지 */ .gradio-radio label[aria-checked="false"] { color: #000 !important; background-color: #fff !important; /* 흰색 배경 유지 */ border: 1px solid #000 !important; /* 검은색 테두리 추가 */ } /* 라디오 버튼의 선택 표시 점 숨기기 */ .gradio-radio input[type="radio"] + label::before { display: none; } /* 라디오 버튼 선택 시 선택 표시 점을 숨기기 */ .gradio-radio input[type="radio"]:checked + label::after { content: ""; display: none; } /* 버튼 텍스트 색상도 검은색으로 변경 */ .btn-primary, .btn-secondary, .gr-button { color: #000 !important; } /* 다운로드 버튼 텍스트 색상 검은색으로 변경 */ .gr-button.download-button { color: #000 !important; } /* 기타 텍스트 요소에 영향을 주지 않도록 주의 */ /* 필요한 경우 추가적인 선택자를 여기에 작성 */ """ # 필터 프리셋 정의 filters = [ { "name": "필터없음", "bg_brightness": 1.0, "bg_contrast": 1.0, "bg_saturation": 1.0, "bg_temperature": 0.0, "bg_vibrance": 0.0, "bg_color_mixer_blues": 0.0, "bg_shadows": 0.0 }, { "name": "깨끗한", "bg_brightness": 1.3, "bg_contrast": 1.2, "bg_saturation": 1.0, "bg_temperature": -0.2, "bg_vibrance": -20.0, "bg_color_mixer_blues": 5.0, "bg_shadows": -10.0 }, { "name": "따스한", "bg_brightness": 1.3, "bg_contrast": 1.2, "bg_saturation": 0.8, "bg_temperature": 0.0, "bg_vibrance": -10.0, "bg_color_mixer_blues": 2.0, "bg_shadows": -10.0 }, { "name": "푸른빛", "bg_brightness": 1.3, "bg_contrast": 1.0, "bg_saturation": 1.0, "bg_temperature": 0.0, "bg_vibrance": 0.0, "bg_color_mixer_blues": 10.0, "bg_shadows": 5.0 } ] # 기본 필터 설정 (깨끗한) default_filter = next(filter for filter in filters if filter["name"] == "깨끗한") with gr.Blocks(theme=gr.themes.Soft( primary_hue=gr.themes.Color( c50="#FFF7ED", # 가장 밝은 주황 c100="#FFEDD5", c200="#FED7AA", c300="#FDBA74", c400="#FB923C", c500="#F97316", # 기본 주황 c600="#EA580C", c700="#C2410C", c800="#9A3412", c900="#7C2D12", # 가장 어두운 주황 c950="#431407", ), secondary_hue="zinc", # 모던한 느낌의 회색 계열 neutral_hue="zinc", font=("Pretendard", "sans-serif") ), css=css) as demo: with gr.Row(): # 왼쪽 열: 입력과 설정 with gr.Column(scale=1): input_image = gr.Image( label="이미지 추가", image_mode="RGBA", type="pil", ) prompt = gr.Textbox( label="프롬프트 (한국어)", placeholder="눈덮인 히말라야, 뭉게구름, 흰색 바닥에 놓여있는 양말", ) run_button = gr.Button( value="이미지 생성", variant="primary", size="lg" ) reset_button = gr.Button( value="필터 초기화", variant="secondary", size="lg" ) # 필터 선택 라디오 버튼 추가 filter_radio = gr.Radio( label="필터 선택", choices=["필터없음", "깨끗한", "따스한", "푸른빛"], value="깨끗한", # 기본 선택은 깨끗한 type="value", ) # 예시 추가 examples = [ ["img/1.png", "눈, 히말라야", "푸른빛"], ["img/2.png", "흰색 벽지에 기대어 있는 사람", "깨끗한"], ["img/3.png", "단순한 책상위에 놓인 미니 파우치", "따스한"], ] gr.Examples( examples=examples, inputs=[input_image, prompt, filter_radio], fn=lambda img, prm, flt: on_generate(img, prm, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0) if flt == "필터없음" else on_generate(img, prm, next(filter for filter in filters if filter["name"] == flt)["bg_brightness"], next(filter for filter in filters if filter["name"] == flt)["bg_contrast"], next(filter for filter in filters if filter["name"] == flt)["bg_saturation"], next(filter for filter in filters if filter["name"] == flt)["bg_temperature"], next(filter for filter in filters if filter["name"] == flt)["bg_vibrance"], next(filter for filter in filters if filter["name"] == flt)["bg_color_mixer_blues"], next(filter for filter in filters if filter["name"] == flt)["bg_shadows"], ), outputs=[ "background_output", "mask_state", "final_output", "image_rgb_state", "initial_result", ], label="예시 선택", cache_examples=False, ) # 배경 조정 옵션 순서 변경 (라벨에서 "배경" 제거) bg_brightness = gr.Slider(label="밝기", minimum=0.1, maximum=5.0, value=default_filter["bg_brightness"], step=0.1) bg_contrast = gr.Slider(label="대비", minimum=0.1, maximum=5.0, value=default_filter["bg_contrast"], step=0.1) bg_saturation = gr.Slider(label="채도", minimum=0.0, maximum=5.0, value=default_filter["bg_saturation"], step=0.1) bg_temperature = gr.Slider( # 색온도 슬라이더 label="색온도", minimum=-1.0, maximum=1.0, value=default_filter["bg_temperature"], step=0.1 ) bg_vibrance = gr.Slider(label="활기", minimum=-100.0, maximum=100.0, value=default_filter["bg_vibrance"], step=1.0) bg_color_mixer_blues = gr.Slider(label="컬러 믹서 (블루)", minimum=-100.0, maximum=100.0, value=default_filter["bg_color_mixer_blues"], step=1.0) bg_shadows = gr.Slider(label="그림자", minimum=-100.0, maximum=100.0, value=default_filter["bg_shadows"], step=1.0) # Shadows 슬라이더 # 오른쪽 열: 출력과 다운로드 버튼 with gr.Column(scale=2): background_output = gr.Image( label="생성된 이미지", type="pil", interactive=False # 출력 전용으로 설정 ) final_output = gr.Image( label="필터 적용 이미지", type="pil", interactive=False # 출력 전용으로 설정 ) # "모든 이미지 다운로드" 버튼과 다운로드 출력창을 중앙에 배치 with gr.Row(elem_classes="download-container"): download_button = gr.Button( value="모든 이미지 다운로드", elem_classes="download-button", variant="primary", size="lg" ) with gr.Row(elem_classes="download-container"): download_output = gr.File(label="모든 이미지 다운로드", elem_classes="download-output") # 상태 컴포넌트 mask_state = gr.State() image_rgb_state = gr.State() # image_rgb 상태 추가 initial_result = gr.State() # 필터 선택 시 슬라이더 값 변경 함수 def apply_filter_preset(selected_filter): for filter in filters: if filter["name"] == selected_filter: return ( gr.update(value=filter["bg_brightness"]), gr.update(value=filter["bg_contrast"]), gr.update(value=filter["bg_saturation"]), gr.update(value=filter["bg_temperature"]), gr.update(value=filter["bg_vibrance"]), gr.update(value=filter["bg_color_mixer_blues"]), gr.update(value=filter["bg_shadows"]), ) # 기본값 반환 (필터없음) return ( gr.update(value=1.0), gr.update(value=1.0), gr.update(value=1.0), gr.update(value=0.0), gr.update(value=0.0), gr.update(value=0.0), gr.update(value=0.0), ) # 필터 라디오 버튼 선택 시 슬라이더 값 업데이트 filter_radio.change( fn=apply_filter_preset, inputs=[filter_radio], outputs=[ bg_brightness, bg_contrast, bg_saturation, bg_temperature, bg_vibrance, bg_color_mixer_blues, bg_shadows, ], ) # 이미지 생성 버튼 클릭 시 generate_images 함수 호출 및 프롬프트 번역 def on_generate(image, prompt, bg_brightness, bg_contrast, bg_saturation, bg_temperature, bg_vibrance, bg_color_mixer_blues, bg_shadows): # 프롬프트가 비어있는지 확인 if not prompt.strip(): logger.info("프롬프트가 비어 있습니다. 기본 프롬프트를 사용합니다.") # 기본 프롬프트 설정 final_prompt = "high-quality professional studio photography, Realistic soft white tone bright lighting, HEIC, CR2, NEF" else: # 한국어 프롬프트를 영어로 번역 translated_prompt = translate_to_english(prompt) if translated_prompt.startswith("유효하지 않은 입력") or translated_prompt.startswith("번역 중 오류"): return None, None, None, None, None # 번역 실패 시 종료 # 번역된 프롬프트에 자동으로 문자열 추가 final_prompt = f"{translated_prompt}, high-quality professional studio photography, Realistic soft white tone bright lighting, HEIC, CR2, NEF" image_rgb, bg_img, mask, result = generate_images(image, final_prompt) print("Image generation completed.") # 슬라이더 값 적용 if bg_img is not None and mask is not None and image_rgb is not None: adjusted_bg = adjust_final_image( bg_image=bg_img, mask=mask, bg_brightness=bg_brightness, bg_contrast=bg_contrast, bg_saturation=bg_saturation, bg_temperature=bg_temperature, bg_vibrance=bg_vibrance, bg_color_mixer_blues=bg_color_mixer_blues, bg_shadows=bg_shadows, ) final_result = Image.composite(image_rgb, adjusted_bg, mask) else: final_result = result # 초기 결과에 final_result 저장 initial_result.value = final_result return bg_img, mask, final_result, image_rgb, final_result # initial_result 추가 반환 run_button.click( fn=on_generate, inputs=[ input_image, prompt, bg_brightness, bg_contrast, bg_saturation, bg_temperature, bg_vibrance, bg_color_mixer_blues, bg_shadows, ], outputs=[ background_output, mask_state, final_output, image_rgb_state, initial_result, # initial_result 추가 반환 ], ) def on_adjust( image_rgb, # image_rgb_state 사용 bg_image, mask, bg_brightness, bg_contrast, bg_saturation, bg_temperature, bg_vibrance, bg_color_mixer_blues, bg_shadows, ): if bg_image is None or mask is None or image_rgb is None: print("Adjust function received None input.") return None print(f"Adjusting background image... Current slider values:") print(f"Brightness: {bg_brightness}, Contrast: {bg_contrast}, Saturation: {bg_saturation}, " f"Temperature: {bg_temperature}, Vibrance: {bg_vibrance}, Blues: {bg_color_mixer_blues}, Shadows: {bg_shadows}") adjusted_bg = adjust_final_image( bg_image=bg_image, mask=mask, # mask를 반드시 전달 bg_brightness=bg_brightness, bg_contrast=bg_contrast, bg_saturation=bg_saturation, bg_temperature=bg_temperature, bg_vibrance=bg_vibrance, bg_color_mixer_blues=bg_color_mixer_blues, bg_shadows=bg_shadows, # Shadows 파라미터 추가 ) # 조정된 배경 이미지 확인 print(f"adjusted_bg size: {adjusted_bg.size}, mode: {adjusted_bg.mode}") adjusted_bg.save("adjusted_bg_debug.jpg") # 디버깅용 임시 파일 저장 # image_rgb은 이미 resize된 상태이므로 추가적인 변환 필요 없음 input_image_rgb = image_rgb # 이미지 크기와 모드 확인 print(f"input_image_rgb size: {input_image_rgb.size}, mode: {input_image_rgb.mode}") print(f"adjusted_bg size: {adjusted_bg.size}, mode: {adjusted_bg.mode}") print(f"mask size: {mask.size}, mode: {mask.mode}") # 이미지 크기가 동일한지 확인하고, 다르면 조정 if input_image_rgb.size != adjusted_bg.size: print(f"Resizing input_image_rgb from {input_image_rgb.size} to {adjusted_bg.size}") input_image_rgb = input_image_rgb.resize(adjusted_bg.size) # mask를 단일 채널로 변환 (필요시) mask = mask.convert("L") try: final_result = Image.composite(input_image_rgb, adjusted_bg, mask) except ValueError as e: print(f"Composite error: {e}") return None # 최종 결과 확인 final_result.save("final_result_debug.jpg") # 디버깅용 임시 파일 저장 print("Final image composite completed.") # 초기 결과에 final_result 저장 initial_result.value = final_result return final_result bg_sliders = [ bg_brightness, bg_contrast, bg_saturation, bg_temperature, bg_vibrance, bg_color_mixer_blues, bg_shadows, # Shadows 슬라이더 추가 ] for slider in bg_sliders: slider.change( fn=on_adjust, inputs=[ image_rgb_state, # image_rgb_state 사용 background_output, # 생성된 이미지 mask_state, # 알파 채널 bg_brightness, bg_contrast, bg_saturation, bg_temperature, bg_vibrance, bg_color_mixer_blues, bg_shadows, # Shadows 파라미터 추가 ], outputs=final_output, # 최종 결과는 합성된 이미지 ) # 리셋 버튼 클릭 시 모든 슬라이더를 "필터없음"의 초기값으로 초기화하고 필터 선택을 "필터없음"으로 변경 reset_button.click( fn=reset_sliders, inputs=[final_output], outputs=[ filter_radio, # 필터 선택 라디오 버튼 bg_brightness, bg_contrast, bg_saturation, bg_temperature, bg_vibrance, bg_color_mixer_blues, bg_shadows, final_output, ], ) # 다운로드 버튼 클릭 시 모든 이미지 다운로드 download_button.click( fn=download_all, inputs=[ input_image, background_output, final_output ], outputs=download_output, ) return demo if __name__ == "__main__": logger.info("애플리케이션 시작") demo = create_interface() demo.queue() demo.launch(server_name='0.0.0.0')