import cv2 import numpy as np from PIL import Image, ImageEnhance, ImageFilter import gradio as gr from io import BytesIO import tempfile import logging # 로깅 설정 - INFO 레벨로 변경 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def adjust_brightness(image, value): """이미지 밝기 조절""" value = float(value - 1) * 100 # 0-2 범위를 -100에서 +100으로 변환 hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) h, s, v = cv2.split(hsv) v = cv2.add(v, value) v = np.clip(v, 0, 255) final_hsv = cv2.merge((h, s, v)) return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR) def adjust_contrast(image, value): """이미지 대비 조절""" value = float(value) return np.clip(image * value, 0, 255).astype(np.uint8) def adjust_saturation(image, value): """이미지 채도 조절""" value = float(value - 1) * 100 # 0-2 범위를 -100에서 +100으로 변환 hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) h, s, v = cv2.split(hsv) s = cv2.add(s, value) s = np.clip(s, 0, 255) final_hsv = cv2.merge((h, s, v)) return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR) def adjust_temperature(image, value): """이미지 색온도 조절 (색상 밸런스)""" value = float(value) * 30 # 효과 스케일 조절 b, g, r = cv2.split(image) if value > 0: # 따뜻하게 r = cv2.add(r, value) b = cv2.subtract(b, value) else: # 차갑게 r = cv2.add(r, value) b = cv2.subtract(b, value) r = np.clip(r, 0, 255) b = np.clip(b, 0, 255) return cv2.merge([b, g, r]) def adjust_tint(image, value): """이미지 색조 조절""" hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) h, s, v = cv2.split(hsv_image) h = cv2.add(h, int(value)) h = np.clip(h, 0, 179) # Hue 값은 0-179 범위 final_hsv = cv2.merge((h, s, v)) return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR) def adjust_exposure(image, value): """이미지 노출 조절""" enhancer = ImageEnhance.Brightness(Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))) img_enhanced = enhancer.enhance(1 + float(value) / 5.0) return cv2.cvtColor(np.array(img_enhanced), cv2.COLOR_RGB2BGR) def adjust_vibrance(image, value): """이미지 활기 조절""" img = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) converter = ImageEnhance.Color(img) factor = 1 + (float(value) / 100.0) img = converter.enhance(factor) return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) def adjust_color_mixer_blues(image, value): """이미지 컬러 믹서 (블루) 조절""" b, g, r = cv2.split(image) b = cv2.add(b, float(value)) b = np.clip(b, 0, 255) return cv2.merge([b, g, r]) def adjust_shadows(image, value): """이미지 그림자 조절""" pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) enhancer = ImageEnhance.Brightness(pil_image) factor = 1 + (float(value) / 100.0) pil_image = enhancer.enhance(factor) return cv2.cvtColor(np.array(pil_image), cv2.COLOR_BGR2RGB) def process_image(image, brightness, contrast, saturation, temperature, tint, exposure, vibrance, color_mixer_blues, shadows): """모든 조정 사항을 이미지에 적용""" if image is None: return None # PIL 이미지를 OpenCV 형식으로 변환 image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) # 조정 사항 순차 적용 image = adjust_brightness(image, brightness) image = adjust_contrast(image, contrast) image = adjust_saturation(image, saturation) image = adjust_temperature(image, temperature) image = adjust_tint(image, tint) image = adjust_exposure(image, exposure) image = adjust_vibrance(image, vibrance) image = adjust_color_mixer_blues(image, color_mixer_blues) image = adjust_shadows(image, shadows) # PIL 이미지로 다시 변환 return Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) def download_image(image, input_image_name): """이미지를 JPG 형식으로 저장하고 경로 반환""" if image is None: return None # 한국 시간 타임스탬프 생성 함수 from datetime import datetime, timedelta def get_korean_timestamp(): korea_time = datetime.utcnow() + timedelta(hours=9) return korea_time.strftime('%Y%m%d_%H%M%S') timestamp = get_korean_timestamp() if input_image_name and hasattr(input_image_name, 'name'): base_name = input_image_name.name.split('.')[0] # 파일 객체에서 이름 추출 else: base_name = "이미지" file_name = f"[끝장AI]끝장필터_{base_name}_{timestamp}.jpg" # 파일 저장 temp_file_path = tempfile.gettempdir() + "/" + file_name image.save(temp_file_path, format="JPEG") return temp_file_path def create_interface(): css = """ footer { visibility: hidden; } .download-button, .download-output { width: 100%; } .download-container { display: flex; flex-direction: column; align-items: center; width: 100%; } #gradio-app { margin: 0 !important; /* 모든 방향 여백 제거 */ text-align: left !important; /* 왼쪽 정렬 강제 */ padding: 20px !important; /* 패딩 추가 */ } .gradio-container { max-width: 100% !important; /* 가로 폭 전체 사용 */ margin-left: 0 !important; /* 왼쪽 정렬 */ padding: 20px !important; /* 패딩 추가 */ } .download-button { background-color: black !important; color: white !important; border: none !important; padding: 10px !important; font-size: 16px !important; } """ 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 interface: with gr.Row(): # 왼쪽 열: 비율 3 with gr.Column(scale=3): # 이미지 업로드 input_image = gr.Image(type="pil", label="이미지 업로드") # 조정 슬라이더 brightness_slider = gr.Slider(0.0, 2.0, value=1.0, step=0.1, label="밝기 조절") contrast_slider = gr.Slider(0.5, 1.5, value=1.0, step=0.1, label="대비 조절") saturation_slider = gr.Slider(0.0, 2.0, value=1.0, step=0.1, label="채도 조절") temperature_slider = gr.Slider(-1.0, 1.0, value=0.0, step=0.1, label="색온도 조절") tint_slider = gr.Slider(-100, 100, value=0, step=1, label="색조 조절") exposure_slider = gr.Slider(-5.0, 5.0, value=0.0, step=0.1, label="노출 조절") vibrance_slider = gr.Slider(-100.0, 100.0, value=0.0, step=1.0, label="활기 조절") color_mixer_blues_slider = gr.Slider(-100.0, 100.0, value=0.0, step=1.0, label="컬러 믹서 (블루)") shadows_slider = gr.Slider(-100.0, 100.0, value=0.0, step=1.0, label="그림자 조절") # 오른쪽 열: 비율 7 with gr.Column(scale=7): # 처리된 이미지 출력 output_image = gr.Image(type="pil", label="처리된 이미지") # 변환된 이미지 다운로드 버튼 with gr.Row(elem_classes="download-container"): download_button = gr.Button("JPG로 변환하기", elem_classes="download-button") with gr.Row(elem_classes="download-container"): download_output = gr.File(label="JPG 이미지 다운로드", elem_classes="download-output") # 이미지 처리 함수 연결 inputs = [ input_image, brightness_slider, contrast_slider, saturation_slider, temperature_slider, tint_slider, exposure_slider, vibrance_slider, color_mixer_blues_slider, shadows_slider ] input_components = [ brightness_slider, contrast_slider, saturation_slider, temperature_slider, tint_slider, exposure_slider, vibrance_slider, color_mixer_blues_slider, shadows_slider ] for input_component in input_components: input_component.change( fn=process_image, inputs=inputs, outputs=output_image ) # 다운로드 버튼 기능 download_button.click( fn=download_image, inputs=[output_image, input_image], outputs=download_output ) return interface # 인터페이스 생성 및 실행 if __name__ == "__main__": logger.info("애플리케이션 시작") interface = create_interface() interface.queue() interface.launch()