Spaces:
Sleeping
Sleeping
import os | |
import tempfile | |
from PIL import Image, ImageEnhance, ImageFilter | |
import gradio as gr | |
import logging | |
import re | |
import time | |
import cv2 | |
import numpy as np | |
from io import BytesIO | |
from datetime import datetime, timedelta | |
from google import genai | |
from google.genai import types | |
from dotenv import load_dotenv | |
load_dotenv() | |
# ๋ก๊น ์ค์ | |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
logger = logging.getLogger(__name__) | |
# ------------------- API ํค ์ํ ์์คํ ------------------- | |
API_KEYS = [] # API ํค ๋ชฉ๋ก | |
current_key_index = 0 # ํ์ฌ ์ฌ์ฉ ์ค์ธ ํค ์ธ๋ฑ์ค | |
def initialize_api_keys(): | |
"""API ํค ๋ชฉ๋ก์ ์ด๊ธฐํํ๋ ํจ์""" | |
global API_KEYS | |
# ํ๊ฒฝ ๋ณ์์์ API ํค ๊ฐ์ ธ์ค๊ธฐ | |
key1 = os.environ.get("GEMINI_API_KEY_1", "") | |
key2 = os.environ.get("GEMINI_API_KEY_2", "") | |
key3 = os.environ.get("GEMINI_API_KEY_3", "") | |
key4 = os.environ.get("GEMINI_API_KEY_4", "") | |
key5 = os.environ.get("GEMINI_API_KEY_5", "") | |
# ๋น ๋ฌธ์์ด์ด ์๋ ํค๋ง ์ถ๊ฐ | |
if key1: | |
API_KEYS.append(key1) | |
if key2: | |
API_KEYS.append(key2) | |
if key3: | |
API_KEYS.append(key3) | |
if key4: | |
API_KEYS.append(key4) | |
if key5: | |
API_KEYS.append(key5) | |
# ๊ธฐ์กด GEMINI_API_KEY๊ฐ ์์ผ๋ฉด ์ถ๊ฐ | |
default_key = os.environ.get("GEMINI_API_KEY", "") | |
if default_key and default_key not in API_KEYS: | |
API_KEYS.append(default_key) | |
logger.info(f"API ํค {len(API_KEYS)}๊ฐ๊ฐ ๋ก๋๋์์ต๋๋ค.") | |
def get_next_api_key(): | |
"""๋ค์ API ํค๋ฅผ ๊ฐ์ ธ์ค๋ ํจ์""" | |
global current_key_index | |
if not API_KEYS: | |
return None | |
# ํ์ฌ ํค ๊ฐ์ ธ์ค๊ธฐ | |
api_key = API_KEYS[current_key_index] | |
# ๋ค์ ํค ์ธ๋ฑ์ค๋ก ์ ๋ฐ์ดํธ | |
current_key_index = (current_key_index + 1) % len(API_KEYS) | |
return api_key | |
# ========== ์ด๋ฏธ์ง ์์ฑ๊ธฐ ๊ด๋ จ ํจ์ ========== | |
def save_binary_file(file_name, data): | |
with open(file_name, "wb") as f: | |
f.write(data) | |
def translate_prompt_to_english(prompt): | |
if not re.search("[๊ฐ-ํฃ]", prompt): | |
return prompt | |
prompt = prompt.replace("#1", "IMAGE_TAG_ONE") | |
prompt = prompt.replace("#2", "IMAGE_TAG_TWO") | |
prompt = prompt.replace("#3", "IMAGE_TAG_THREE") | |
try: | |
api_key = get_next_api_key() | |
if not api_key: | |
logger.error("Gemini API ํค๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค.") | |
prompt = prompt.replace("IMAGE_TAG_ONE", "#1") | |
prompt = prompt.replace("IMAGE_TAG_TWO", "#2") | |
prompt = prompt.replace("IMAGE_TAG_THREE", "#3") | |
return prompt | |
client = genai.Client(api_key=api_key) | |
translation_prompt = f""" | |
Translate the following Korean text to English: | |
{prompt} | |
IMPORTANT: The tokens IMAGE_TAG_ONE, IMAGE_TAG_TWO, and IMAGE_TAG_THREE are special tags | |
and must be preserved exactly as is in your translation. Do not translate these tokens. | |
""" | |
logger.info(f"Translation prompt: {translation_prompt}") | |
response = client.models.generate_content( | |
model="gemini-2.0-flash", | |
contents=[translation_prompt], | |
config=types.GenerateContentConfig( | |
response_modalities=['Text'], | |
temperature=0.2, | |
top_p=0.95, | |
top_k=40, | |
max_output_tokens=512 | |
) | |
) | |
translated_text = "" | |
for part in response.candidates[0].content.parts: | |
if hasattr(part, 'text') and part.text: | |
translated_text += part.text | |
if translated_text.strip(): | |
translated_text = translated_text.replace("IMAGE_TAG_ONE", "#1") | |
translated_text = translated_text.replace("IMAGE_TAG_TWO", "#2") | |
translated_text = translated_text.replace("IMAGE_TAG_THREE", "#3") | |
logger.info(f"Translated text: {translated_text.strip()}") | |
return translated_text.strip() | |
else: | |
logger.warning("๋ฒ์ญ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค. ์๋ณธ ํ๋กฌํํธ ์ฌ์ฉ") | |
prompt = prompt.replace("IMAGE_TAG_ONE", "#1") | |
prompt = prompt.replace("IMAGE_TAG_TWO", "#2") | |
prompt = prompt.replace("IMAGE_TAG_THREE", "#3") | |
return prompt | |
except Exception as e: | |
logger.exception("๋ฒ์ญ ์ค ์ค๋ฅ ๋ฐ์:") | |
prompt = prompt.replace("IMAGE_TAG_ONE", "#1") | |
prompt = prompt.replace("IMAGE_TAG_TWO", "#2") | |
prompt = prompt.replace("IMAGE_TAG_THREE", "#3") | |
return prompt | |
def preprocess_prompt(prompt, image1, image2, image3): | |
# ๊ธฐ์กด ํจ์ ์ ์ง | |
has_img1 = image1 is not None | |
has_img2 = image2 is not None | |
has_img3 = image3 is not None | |
if "#1" in prompt and not has_img1: | |
prompt = prompt.replace("#1", "์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง(์์)") | |
else: | |
prompt = prompt.replace("#1", "์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง") | |
if "#2" in prompt and not has_img2: | |
prompt = prompt.replace("#2", "๋ ๋ฒ์งธ ์ด๋ฏธ์ง(์์)") | |
else: | |
prompt = prompt.replace("#2", "๋ ๋ฒ์งธ ์ด๋ฏธ์ง") | |
if "#3" in prompt and not has_img3: | |
prompt = prompt.replace("#3", "์ธ ๋ฒ์งธ ์ด๋ฏธ์ง(์์)") | |
else: | |
prompt = prompt.replace("#3", "์ธ ๋ฒ์งธ ์ด๋ฏธ์ง") | |
if "1. ์ด๋ฏธ์ง ๋ณ๊ฒฝ" in prompt: | |
desc_match = re.search(r'#1์ "(.*?)"์ผ๋ก ๋ฐ๊ฟ๋ผ', prompt) | |
if desc_match: | |
description = desc_match.group(1) | |
prompt = f"์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง๋ฅผ {description}์ผ๋ก ๋ณ๊ฒฝํด์ฃผ์ธ์. ์๋ณธ ์ด๋ฏธ์ง์ ์ฃผ์ ๋ด์ฉ์ ์ ์งํ๋ ์๋ก์ด ์คํ์ผ๊ณผ ๋ถ์๊ธฐ๋ก ์ฌํด์ํด์ฃผ์ธ์." | |
else: | |
prompt = "์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง๋ฅผ ์ฐฝ์์ ์ผ๋ก ๋ณํํด์ฃผ์ธ์. ๋ ์์ํ๊ณ ์์ ์ ์ธ ๋ฒ์ ์ผ๋ก ๋ง๋ค์ด์ฃผ์ธ์." | |
elif "2. ๊ธ์์ง์ฐ๊ธฐ" in prompt: | |
text_match = re.search(r'#1์์ "(.*?)"๋ฅผ ์ง์๋ผ', prompt) | |
if text_match: | |
text_to_remove = text_match.group(1) | |
prompt = f"์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง์์ '{text_to_remove}' ํ ์คํธ๋ฅผ ์ฐพ์ ์์ฐ์ค๋ฝ๊ฒ ์ ๊ฑฐํด์ฃผ์ธ์. ํ ์คํธ๊ฐ ์๋ ๋ถ๋ถ์ ๋ฐฐ๊ฒฝ๊ณผ ์กฐํ๋กญ๊ฒ ์ฑ์์ฃผ์ธ์." | |
else: | |
prompt = "์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง์์ ๋ชจ๋ ํ ์คํธ๋ฅผ ์ฐพ์ ์์ฐ์ค๋ฝ๊ฒ ์ ๊ฑฐํด์ฃผ์ธ์. ๊น๋ํ ์ด๋ฏธ์ง๋ก ๋ง๋ค์ด์ฃผ์ธ์." | |
elif "4. ์ท๋ฐ๊พธ๊ธฐ" in prompt: | |
prompt = "์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง์ ์ธ๋ฌผ ์์์ ๋ ๋ฒ์งธ ์ด๋ฏธ์ง์ ์์์ผ๋ก ๋ณ๊ฒฝํด์ฃผ์ธ์. ์์์ ์คํ์ผ๊ณผ ์์์ ๋ ๋ฒ์งธ ์ด๋ฏธ์ง๋ฅผ ๋ฐ๋ฅด๋, ์ ์ฒด ๋น์จ๊ณผ ํฌ์ฆ๋ ์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง๋ฅผ ์ ์งํด์ฃผ์ธ์." | |
elif "5. ๋ฐฐ๊ฒฝ๋ฐ๊พธ๊ธฐ" in prompt: | |
prompt = "์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง์ ๋ฐฐ๊ฒฝ์ ๋ ๋ฒ์งธ ์ด๋ฏธ์ง์ ๋ฐฐ๊ฒฝ์ผ๋ก ๋ณ๊ฒฝํด์ฃผ์ธ์. ์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง์ ์ฃผ์ ํผ์ฌ์ฒด๋ ์ ์งํ๊ณ , ๋ ๋ฒ์งธ ์ด๋ฏธ์ง์ ๋ฐฐ๊ฒฝ๊ณผ ์กฐํ๋กญ๊ฒ ํฉ์ฑํด์ฃผ์ธ์." | |
elif "6. ์ด๋ฏธ์ง ํฉ์ฑ(์ํํฌํจ)" in prompt: | |
prompt = "์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง์ ๋ ๋ฒ์งธ ์ด๋ฏธ์ง(๋๋ ์ธ ๋ฒ์งธ ์ด๋ฏธ์ง)๋ฅผ ์์ฐ์ค๋ฝ๊ฒ ํฉ์ฑํด์ฃผ์ธ์. ๋ชจ๋ ์ด๋ฏธ์ง์ ์ฃผ์ ์์๋ฅผ ํฌํจํ๊ณ , ํนํ ์ํ์ด ๋๋ณด์ด๋๋ก ์กฐํ๋กญ๊ฒ ํตํฉํด์ฃผ์ธ์." | |
prompt += " ์ด๋ฏธ์ง๋ฅผ ์์ฑํด์ฃผ์ธ์. ์ด๋ฏธ์ง์ ํ ์คํธ๋ ๊ธ์๋ฅผ ํฌํจํ์ง ๋ง์ธ์." | |
return prompt | |
def generate_with_images(prompt, images, variation_index=0): | |
try: | |
api_key = get_next_api_key() | |
if not api_key: | |
return None, "API ํค๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค. ํ๊ฒฝ ๋ณ์์ GEMINI_API_KEY_1, GEMINI_API_KEY_2, GEMINI_API_KEY_3, GEMINI_API_KEY_4, GEMINI_API_KEY_5 ์ค ํ๋ ์ด์์ ์ค์ ํด์ฃผ์ธ์." | |
client = genai.Client(api_key=api_key) | |
logger.info(f"Gemini API ์์ฒญ ์์ - ํ๋กฌํํธ: {prompt}, ๋ณํ ์ธ๋ฑ์ค: {variation_index}") | |
variation_suffixes = [ | |
" Create this as the first variation. Do not add any text, watermarks, or labels to the image.", | |
" Create this as the second variation with more vivid colors. Do not add any text, watermarks, or labels to the image.", | |
" Create this as the third variation with a more creative style. Do not add any text, watermarks, or labels to the image.", | |
" Create this as the fourth variation with enhanced details. Do not add any text, watermarks, or labels to the image." | |
] | |
if variation_index < len(variation_suffixes): | |
prompt = prompt + variation_suffixes[variation_index] | |
else: | |
prompt = prompt + " Do not add any text, watermarks, or labels to the image." | |
contents = [prompt] | |
for idx, img in enumerate(images, 1): | |
if img is not None: | |
contents.append(img) | |
logger.info(f"์ด๋ฏธ์ง #{idx} ์ถ๊ฐ๋จ") | |
response = client.models.generate_content( | |
model="gemini-2.0-flash-exp-image-generation", | |
contents=contents, | |
config=types.GenerateContentConfig( | |
response_modalities=['Text', 'Image'], | |
temperature=1, | |
top_p=0.95, | |
top_k=40, | |
max_output_tokens=8192 | |
) | |
) | |
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp: | |
temp_path = tmp.name | |
result_text = "" | |
image_found = False | |
for part in response.candidates[0].content.parts: | |
if hasattr(part, 'text') and part.text: | |
result_text += part.text | |
logger.info(f"์๋ต ํ ์คํธ: {part.text}") | |
elif hasattr(part, 'inline_data') and part.inline_data: | |
save_binary_file(temp_path, part.inline_data.data) | |
image_found = True | |
logger.info("์๋ต์์ ์ด๋ฏธ์ง ์ถ์ถ ์ฑ๊ณต") | |
if not image_found: | |
return None, f"API์์ ์ด๋ฏธ์ง๋ฅผ ์์ฑํ์ง ๋ชปํ์ต๋๋ค. ์๋ต ํ ์คํธ: {result_text}" | |
result_img = Image.open(temp_path) | |
if result_img.mode == "RGBA": | |
result_img = result_img.convert("RGB") | |
return result_img, f"์ด๋ฏธ์ง๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์์ฑ๋์์ต๋๋ค. {result_text}" | |
except Exception as e: | |
logger.exception("์ด๋ฏธ์ง ์์ฑ ์ค ์ค๋ฅ ๋ฐ์:") | |
return None, f"์ค๋ฅ ๋ฐ์: {str(e)}" | |
def process_images_with_prompt(image1, image2, image3, prompt, variation_index=0, max_retries=3): | |
retry_count = 0 | |
last_error = None | |
while retry_count < max_retries: | |
try: | |
images = [image1, image2, image3] | |
valid_images = [img for img in images if img is not None] | |
if not valid_images: | |
return None, "์ ์ด๋ ํ๋์ ์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ํด์ฃผ์ธ์.", "" | |
if prompt and prompt.strip(): | |
processed_prompt = preprocess_prompt(prompt, image1, image2, image3) | |
if re.search("[๊ฐ-ํฃ]", processed_prompt): | |
final_prompt = translate_prompt_to_english(processed_prompt) | |
else: | |
final_prompt = processed_prompt | |
else: | |
if len(valid_images) == 1: | |
final_prompt = "Please creatively transform this image into a more vivid and artistic version. Do not include any text or watermarks in the generated image." | |
logger.info("Default prompt generated for single image") | |
elif len(valid_images) == 2: | |
final_prompt = "Please seamlessly composite these two images, integrating their key elements harmoniously into a single image. Do not include any text or watermarks in the generated image." | |
logger.info("Default prompt generated for two images") | |
else: | |
final_prompt = "Please creatively composite these three images, combining their main elements into a cohesive and natural scene. Do not include any text or watermarks in the generated image." | |
logger.info("Default prompt generated for three images") | |
result_img, status = generate_with_images(final_prompt, valid_images, variation_index) | |
if result_img is not None: | |
return result_img, status, final_prompt | |
else: | |
last_error = status | |
retry_count += 1 | |
logger.warning(f"์ด๋ฏธ์ง ์์ฑ ์คํจ, ์ฌ์๋ {retry_count}/{max_retries}: {status}") | |
time.sleep(1) | |
except Exception as e: | |
last_error = str(e) | |
retry_count += 1 | |
logger.exception(f"์ด๋ฏธ์ง ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์, ์ฌ์๋ {retry_count}/{max_retries}:") | |
time.sleep(1) | |
return None, f"์ต๋ ์ฌ์๋ ํ์({max_retries}ํ) ์ด๊ณผ ํ ์คํจ: {last_error}", prompt | |
def generate_multiple_images(image1, image2, image3, prompt, progress=gr.Progress()): | |
results = [] | |
statuses = [] | |
prompts = [] | |
num_images = 4 | |
max_retries = 3 | |
progress(0, desc="์ด๋ฏธ์ง ์์ฑ ์ค๋น ์ค...") | |
for i in range(num_images): | |
progress((i / num_images), desc=f"{i+1}/{num_images} ์ด๋ฏธ์ง ์์ฑ ์ค...") | |
result_img, status, final_prompt = process_images_with_prompt(image1, image2, image3, prompt, i, max_retries) | |
if result_img is not None: | |
results.append(result_img) | |
statuses.append(f"์ด๋ฏธ์ง #{i+1}: {status}") | |
prompts.append(f"์ด๋ฏธ์ง #{i+1}: {final_prompt}") | |
else: | |
results.append(None) | |
statuses.append(f"์ด๋ฏธ์ง #{i+1} ์์ฑ ์คํจ: {status}") | |
prompts.append(f"์ด๋ฏธ์ง #{i+1}: {final_prompt}") | |
time.sleep(1) | |
progress(1.0, desc="์ด๋ฏธ์ง ์์ฑ ์๋ฃ!") | |
while len(results) < 4: | |
results.append(None) | |
combined_status = "\n".join(statuses) | |
combined_prompts = "\n".join(prompts) | |
return results[0], results[1], results[2], results[3], combined_status, combined_prompts | |
# ========== ์ด๋ฏธ์ง ํธ์ง๊ธฐ ๊ด๋ จ ํจ์ ========== | |
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_edited_image(image, input_image_name): | |
"""์ด๋ฏธ์ง๋ฅผ JPG ํ์์ผ๋ก ์ ์ฅํ๊ณ ๊ฒฝ๋ก ๋ฐํ""" | |
if image is None: | |
return None | |
# ํ๊ตญ ์๊ฐ ํ์์คํฌํ ์์ฑ | |
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 | |
# ์ปค์คํ CSS ์คํ์ผ - ์๋ก์ด ์คํ์ผ ์ ์ฉ | |
custom_css = """ | |
:root { | |
--primary-color: #FB7F0D; | |
--secondary-color: #ff9a8b; | |
--accent-color: #FF6B6B; | |
--background-color: #FFF3E9; | |
--card-bg: #ffffff; | |
--text-color: #334155; | |
--border-radius: 18px; | |
--shadow: 0 8px 30px rgba(251, 127, 13, 0.08); | |
} | |
body { | |
font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif; | |
background-color: var(--background-color); | |
color: var(--text-color); | |
line-height: 1.6; | |
} | |
/* Gradio ์ปจํ ์ด๋ ์ค๋ฒ๋ผ์ด๋ */ | |
.gradio-container { | |
max-width: 100% !important; /* 200%์์ 100%๋ก ๋ณ๊ฒฝ */ | |
width: 100% !important; /* ์ถ๊ฐ: ๋๋น 100% ์ง์ */ | |
margin: 0 auto !important; | |
padding: 0 !important; | |
background-color: var(--background-color) !important; | |
box-sizing: border-box !important; /* ์ถ๊ฐ: ํจ๋ฉ์ด ๋๋น์ ํฌํจ๋๋๋ก ์ค์ */ | |
} | |
/* ์ถ๊ฐ: ๋ด๋ถ ์ปจํ ์ด๋๋ 100% ๋๋น๋ก ์ค์ */ | |
.contain { | |
max-width: 100% !important; | |
width: 100% !important; | |
} | |
/* ์ถ๊ฐ: ๊ฐ ํ(Row)๋ 100% ๋๋น๋ก ์ค์ */ | |
.gr-padded { | |
padding: 0 !important; | |
width: 100% !important; | |
max-width: 100% !important; | |
} | |
/* ํจ๋ ์คํ์ผ๋ง */ | |
.gr-group { | |
background-color: var(--card-bg); | |
border-radius: var(--border-radius) !important; | |
box-shadow: var(--shadow) !important; | |
padding: 1.5rem !important; | |
margin-bottom: 1.5rem !important; | |
border: 1px solid rgba(0, 0, 0, 0.04) !important; | |
transition: transform 0.3s ease; | |
} | |
.gr-group:hover { | |
transform: translateY(-5px); | |
} | |
/* ์น์ ์ ๋ชฉ */ | |
.section-title { | |
font-size: 22px !important; | |
font-weight: 700 !important; | |
color: #333333 !important; | |
margin-bottom: 1rem !important; | |
padding-bottom: 0.5rem !important; | |
border-bottom: 2px solid var(--primary-color) !important; | |
display: flex; | |
align-items: center; | |
} | |
.section-title span { | |
color: var(--primary-color); | |
} | |
/* ๋ฒํผ ์คํ์ผ๋ง */ | |
.custom-button { | |
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important; | |
color: white !important; | |
font-weight: 600 !important; | |
border: none !important; | |
border-radius: 30px !important; | |
padding: 12px 24px !important; | |
box-shadow: 0 4px 8px rgba(251, 127, 13, 0.25) !important; | |
transition: all 0.3s ease !important; | |
text-transform: none !important; | |
display: flex !important; | |
align-items: center !important; | |
justify-content: center !important; | |
} | |
.custom-button:hover { | |
transform: translateY(-2px) !important; | |
box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3) !important; | |
} | |
.custom-button.primary { | |
background: linear-gradient(135deg, var(--accent-color), #ff9a8b) !important; | |
} | |
/* ์ด๋ฏธ์ง ์ปจํ ์ด๋ */ | |
.image-container { | |
border-radius: var(--border-radius); | |
overflow: hidden; | |
border: 1px solid rgba(0, 0, 0, 0.08); | |
transition: all 0.3s ease; | |
background-color: white; | |
} | |
.image-container:hover { | |
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); | |
} | |
/* ํญ ์คํ์ผ ๊ฐ์ */ | |
.tabs { | |
border-bottom: none !important; | |
} | |
.tab-nav { | |
background-color: transparent !important; | |
border-bottom: 1px solid #eeeeee !important; | |
padding: 0 !important; | |
} | |
.tab-nav button { | |
border-radius: var(--border-radius) var(--border-radius) 0 0 !important; | |
margin-right: 5px !important; | |
padding: 12px 20px !important; | |
font-size: 18px !important; | |
font-weight: 600 !important; | |
border: 1px solid #eeeeee !important; | |
border-bottom: none !important; | |
background-color: rgba(255, 255, 255, 0.7) !important; | |
color: var(--text-color) !important; | |
transition: all 0.3s ease !important; | |
min-width: 150px !important; | |
text-align: center !important; | |
} | |
.tab-nav button.selected { | |
background-color: var(--primary-color) !important; | |
color: white !important; | |
border-color: var(--primary-color) !important; | |
box-shadow: 0 -2px 6px rgba(251, 127, 13, 0.2) !important; | |
} | |
.tab-nav button:hover:not(.selected) { | |
background-color: var(--background-color) !important; | |
border-bottom: none !important; | |
} | |
/* ์ ๋ ฅ ํ๋ ์คํ์ผ */ | |
.gr-input, .gr-text-input, .gr-sample-inputs { | |
border-radius: var(--border-radius) !important; | |
border: 1px solid #dddddd !important; | |
padding: 12px !important; | |
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05) !important; | |
transition: all 0.3s ease !important; | |
} | |
.gr-input:focus, .gr-text-input:focus { | |
border-color: var(--primary-color) !important; | |
outline: none !important; | |
box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important; | |
} | |
/* ๋ฒํผ ๊ทธ๋ฃน */ | |
.button-grid { | |
display: grid; | |
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); | |
gap: 0.8rem; | |
margin-bottom: 1.2rem; | |
} | |
/* ๋ฉ์ธ ์ปจํ ์ธ ์คํฌ๋กค๋ฐ */ | |
::-webkit-scrollbar { | |
width: 8px; | |
height: 8px; | |
} | |
::-webkit-scrollbar-track { | |
background: rgba(0, 0, 0, 0.05); | |
border-radius: 10px; | |
} | |
::-webkit-scrollbar-thumb { | |
background: var(--primary-color); | |
border-radius: 10px; | |
} | |
/* ํธ์ง๊ธฐ ํน๋ณ ์คํ์ผ */ | |
.editor-section { | |
background-color: var(--card-bg); | |
border-radius: var(--border-radius); | |
box-shadow: var(--shadow); | |
padding: 1.5rem; | |
margin-bottom: 1.5rem; | |
} | |
.editor-title { | |
font-size: 1.5rem; | |
font-weight: 700; | |
color: var(--primary-color); | |
margin-bottom: 1rem; | |
display: flex; | |
align-items: center; | |
} | |
.editor-title i { | |
margin-right: 0.5rem; | |
} | |
.download-button { | |
background-color: var(--accent-color) !important; | |
color: white !important; | |
border: none !important; | |
padding: 10px !important; | |
font-size: 12px !important; | |
border-radius: var(--border-radius) !important; | |
font-weight: 600 !important; | |
transition: all 0.3s ease !important; | |
} | |
.download-button:hover { | |
transform: translateY(-2px) !important; | |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15) !important; | |
} | |
.download-container { | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
width: 100%; | |
} | |
.download-output { | |
width: 100%; | |
margin-top: 1rem; | |
} | |
/* ์ ๋๋ฉ์ด์ ์คํ์ผ */ | |
@keyframes fadeIn { | |
from { opacity: 0; transform: translateY(10px); } | |
to { opacity: 1; transform: translateY(0); } | |
} | |
.fade-in { | |
animation: fadeIn 0.5s ease-out; | |
} | |
/* Examples ์น์ ์คํ์ผ */ | |
.examples-section { | |
display: grid; | |
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); | |
gap: 1.5rem; | |
margin-top: 1rem; | |
} | |
.example-item { | |
background-color: white; | |
border-radius: var(--border-radius); | |
overflow: hidden; | |
box-shadow: var(--shadow); | |
transition: transform 0.3s ease; | |
} | |
.example-item:hover { | |
transform: translateY(-5px); | |
} | |
/* ๋ฐ์ํ */ | |
@media (max-width: 768px) { | |
.button-grid { | |
grid-template-columns: repeat(2, 1fr); | |
} | |
.examples-section { | |
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); | |
} | |
} | |
""" # CSS ๋ฌธ์์ด ์ข ๋ฃ | |
# FontAwesome ์์ด์ฝ ํฌํจ | |
fontawesome_link = """ | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" /> | |
""" | |
# ์ ๋ชฉ๊ณผ ์ฌ์ฉ ๊ฐ์ด๋ ์ ๊ฑฐ | |
header_html = "" | |
image_generator_guide_html = "" | |
image_editor_guide_html = "" | |
# UI ๊ตฌ์ฑ | |
with gr.Blocks(css=custom_css, theme=gr.themes.Default( | |
primary_hue="orange", | |
secondary_hue="orange", | |
font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"] | |
)) as demo: | |
gr.HTML(fontawesome_link) | |
# ์ ๋ชฉ ์ ๊ฑฐ | |
# gr.HTML(header_html) | |
with gr.Tabs(elem_classes="tabs") as tabs: | |
# ์ด๋ฏธ์ง ์์ฑ๊ธฐ ํญ | |
with gr.Tab("โจ ์ด๋ฏธ์ง ์์ฑ๊ธฐ", elem_classes="tab-content"): | |
# ์ฌ์ฉ ๊ฐ์ด๋ ์น์ ์ ๊ฑฐ | |
# gr.HTML(image_generator_guide_html) | |
with gr.Row(equal_height=True): | |
with gr.Column(scale=1): | |
# API ํค ์ ๋ ฅ ์น์ ์ ๊ฑฐ | |
# ======== ์ด๋ฏธ์ง ์ ๋ก๋ ๋ฐ ์ค์ ์น์ ======== | |
with gr.Group(): | |
gr.HTML('<div class="section-title"><i class="fas fa-upload"></i> <span>์ด๋ฏธ์ง ์ ๋ก๋ ๋ฐ ์ค์ </span></div>') | |
with gr.Row(): | |
image1_input = gr.Image(type="pil", label="#1", image_mode="RGB", elem_classes="image-container", height=400) | |
image2_input = gr.Image(type="pil", label="#2", image_mode="RGB", elem_classes="image-container", height=400) | |
image3_input = gr.Image(type="pil", label="#3", image_mode="RGB", elem_classes="image-container", height=400) | |
# ํ๋กฌํํธ ์ ๋ ฅ ํ๋ ์ถ๊ฐ | |
prompt_input = gr.Textbox( | |
lines=3, | |
placeholder="ํ๋กฌํํธ๋ฅผ ์ ๋ ฅํ๊ฑฐ๋ ๋น์๋๋ฉด ์๋ ํฉ์ฑ๋ฉ๋๋ค. '#1', '#2', '#3'์ผ๋ก ๊ฐ ์ด๋ฏธ์ง๋ฅผ ์ฐธ์กฐํ ์ ์์ต๋๋ค.", | |
label="ํ๋กฌํํธ (์ ํ ์ฌํญ)", | |
elem_classes="gr-text-input" | |
) | |
# ======== ๋ณํ ์ต์ ์น์ ======== | |
with gr.Group(): | |
gr.HTML('<div class="section-title"><i class="fas fa-sliders-h"></i> <span>ํ๋กฌํํธ ํ ํ๋ฆฟ</span></div>') | |
with gr.Column(elem_classes="button-grid"): | |
image_change_btn1 = gr.Button('๐ ๋ถ๋ถ๋ณ๊ฒฝ-1', elem_classes="custom-button") | |
image_change_btn2 = gr.Button('๐ ๋ถ๋ถ๋ณ๊ฒฝ-2', elem_classes="custom-button") | |
image_change_btn3= gr.Button('๐ ๋ถ๋ถ๋ณ๊ฒฝ-3', elem_classes="custom-button") | |
text_remove_btn = gr.Button('๐งน ๊ธ์์ง์ฐ๊ธฐ', elem_classes="custom-button") | |
text_change_btn = gr.Button('๐ค ๊ธ์๋ณ๊ฒฝ', elem_classes="custom-button") | |
clothes_change_btn1 = gr.Button('๐ ์ํ์ฐฉ์ฉ-1', elem_classes="custom-button") | |
clothes_change_btn2 = gr.Button('๐ ์ํ์ฐฉ์ฉ-2', elem_classes="custom-button") | |
holding_product_btn = gr.Button('๐ท ์ํ๋ค๊ณ ', elem_classes="custom-button") | |
background_change_btn = gr.Button('๐ผ๏ธ ๋ฐฐ๊ฒฝ๋ฐ๊พธ๊ธฐ', elem_classes="custom-button") | |
composite_product_btn = gr.Button('โ๏ธ ๋ถ๋ถ์ง์ฐ๊ธฐ', elem_classes="custom-button") | |
outpainting_btn = gr.Button('๐ ์ด๋ฏธ์งํ์ฅ', elem_classes="custom-button") | |
food_btn_1 = gr.Button('๐ฝ๏ธ ํ๋ ์ดํ -1', elem_classes="custom-button") | |
food_btn_2 = gr.Button('๐ฝ๏ธ ํ๋ ์ดํ -2', elem_classes="custom-button") | |
food_btn_3 = gr.Button('๐ฝ๏ธ ํ๋ ์ดํ -3', elem_classes="custom-button") | |
# ======== ์ด๋ฏธ์ง ์์ฑ ์น์ ======== | |
with gr.Group(): | |
gr.HTML('<div class="section-title"><i class="fas fa-image"></i> <span>์ด๋ฏธ์ง ์์ฑ</span></div>') | |
submit_single_btn = gr.Button('โจ ์ด๋ฏธ์ง ์์ฑ (1์ฅ)', elem_classes="custom-button primary") | |
submit_btn = gr.Button('โจ ์ด๋ฏธ์ง ์์ฑ (4์ฅ)', elem_classes="custom-button primary") | |
with gr.Column(scale=1): | |
# ======== ์์ฑ๋ ์ด๋ฏธ์ง ์น์ ======== | |
with gr.Group(): | |
gr.HTML('<div class="section-title"><i class="fas fa-images"></i> <span>์์ฑ๋ ์ด๋ฏธ์ง</span></div>') | |
with gr.Row(): | |
with gr.Column(): | |
output_image1 = gr.Image(label="์ด๋ฏธ์ง #1", elem_classes="image-container", height=400) | |
output_image3 = gr.Image(label="์ด๋ฏธ์ง #3", elem_classes="image-container", height=400) | |
with gr.Column(): | |
output_image2 = gr.Image(label="์ด๋ฏธ์ง #2", elem_classes="image-container", height=400) | |
output_image4 = gr.Image(label="์ด๋ฏธ์ง #4", elem_classes="image-container", height=400) | |
# ======== ๊ฒฐ๊ณผ ์ ๋ณด ์น์ ======== | |
with gr.Group(): | |
gr.HTML('<div class="section-title"><i class="fas fa-info-circle"></i> <span>๊ฒฐ๊ณผ ์ ๋ณด</span></div>') | |
output_text = gr.Textbox(label="์ํ ๋ฉ์์ง", lines=2, elem_classes="gr-text-input") | |
prompt_display = gr.Textbox(label="์ฌ์ฉ๋ ํ๋กฌํํธ (์์ด)", visible=True, lines=2, elem_classes="gr-text-input") | |
# ======== ์์ ์ด๋ฏธ์ง ์น์ ======== | |
gr.HTML('<div class="section-title"><i class="fas fa-lightbulb"></i> <span>์์ ์ด๋ฏธ์ง</span></div>') | |
# ๋ชจ๋ ์์ ํ ํ์ด์ง์ ํ์ | |
examples = [ | |
["down/๋ชจ๋ธ.jpg", None, None, "(#1์ ์ฌ์ฑ)์ด ์ด์ง ๋ค๋ก ๋์๋ณด๋ ๋ชจ์ต์ผ๋ก ์ต๋ํ ์ด์ seed๋ฅผ ์ ์งํ์ฒด ์์ฐ์ค๋ฝ๊ฒ ๋ณ๊ฒฝํ๋ผ."], | |
["down/์์ด๋ ๊ณ ๋ชจํ.png", None, None, "(#1 ๋ ๋ชจ๋ชจํ)์์ ์ฒญ์์์ด๋ ๊ณ ๋ง ๊ฒ์์ ๊ณ ๋๋ ๊ณ ๋ก ๋ณ๊ฒฝํ๊ณ ๋๋จธ์ง ๋ถ๋ถ์ seed๋ฅผ ๋ณ๊ฒฝ์ ํ์ง๋ง๋ผ."], | |
["down/์ผ์๊ฐ๋ฐฉ.png", None, None, "(#1 ์ฌํ์ฉ ์ผ์๋ฐ์ค)์์ ์ผ์์ด ๋ด๊ธด 3์์ ์ฝ๋ผ๊ฐ ๋์ฌ์๋ ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๋ผ."], | |
["down/์ค๊ตญ์ด.png", None, None, "(#1 ์ด๋ฏธ์ง)์ ์๋ ์ค๊ตญ์ด๋ฅผ ๋ชจ๋ ์ ๊ฑฐํ๋ผ."], | |
["down/ํ ์คํธ.webp", None, None, '(#1์ ํ ์คํธ)๋ฅผ ์คํ์ผ์ ์ ์งํ์ฒด ํ ์คํธ๋ง "Hello"๋ก ๋ฐ๊ฟ๋ผ'], | |
["down/๋ชจ๋ธ2.png", "down/์ ๊ธ๋ผ์ค.png", "down/์ฒญ๋ฐ์ง.png", "(#1์ ์ฌ์ฑ๋ชจ๋ธ)์ด ์ ์ฒด ๋น์จ๊ณผ ํฌ์ฆ๋ ์ ์งํ ์ฒด (#2์ ์ ๊ธ๋ผ์ค)์ (#3์ ์ฒญ๋ฐ์ง)๋ฅผ ์ง์ ๋ชจ๋ธ์ด ์ฐฉ์ฉํ๊ฒ ์ฒ๋ผ ์์ฐ์ค๋ฝ๊ฒ ๋ชจ์ต์ ์์ฑํ๋ผ."], | |
["down/๋ชจ๋ธ2.png", "down/์ ๊ธ๋ผ์ค.png", "down/์นดํ์ ๊ฒฝ.png", "(#1์ ์ฌ์ฑ๋ชจ๋ธ)์ด (#2์ ์ ๊ธ๋ผ์ค)์ ์ฐฉ์ฉํ๊ณ (#3์ ๋ท๋ฐฐ๊ฒฝ์ ์นดํ์ ์ฒด๊ฐ ๋ณด์ด๋ฉฐ) ์์์ ์์ ์๋ ๋ชจ์ต์ ์์ฑํ๋ผ."], | |
["down/๋ชจ๋ธ2.png", "down/์์ธ์.png", None, "(#1์ ์ฌ์ฑ๋ชจ๋ธ)์ด(#2์ ์์ธ์)์ ๋ค๊ณ ์๋ ์์ฐ์ค๋ฌ์ด ๋ชจ์ต์ ์์ฑํ๋ผ."], | |
["down/๋ชจ๋ธ2.png", "down/์นดํ์ ๊ฒฝ.png", None, "(#1์ ์ฌ์ฑ๋ชจ๋ธ)์ด (#2 ์นดํ)์์ ์์ฐ์ค๋ฝ๊ฒ ์๋ ๋ชจ์ต์ ์์ฑํ๋ผ."], | |
["down/์์ด๋ ๊ณ ๋ชจํ.png", None, None, "(#1์ ๋ ๊ณ ๋ชจํ)์์ ์ฒญ์์์ด๋ ๊ณ ๋ฅผ ์ ๊ฑฐํ ํ, ๊ทธ ์๋ฆฌ๋ฅผ ์ฃผ๋ณ ๋ฐฐ๊ฒฝ๊ณผ ์์ฐ์ค๋ฝ๊ฒ ์ด์ฐ๋ฌ์ง๋๋ก ์ฑ์์ฃผ์ธ์. ๋จ, ์ด๋ฏธ์ง์ ๋ค๋ฅธ ๋ถ๋ถ์ ์ฃผ์ ์์๋ ๋์ผํ๊ฒ ์ ์งํด์ผํ๋ค."], | |
["down/์นดํ์ ๊ฒฝ.png", None, None, "(#1 ์ด๋ฏธ์ง)๋ฅผ ์๋ณธ๊ทธ๋๋ก ์ค์์ ๋๊ณ ๋น์จ๋ก ์ ์งํ ์ฒด ์์๋ ๋ฐ ์ข์ฐ๋ก ํฌ๊ฒ ํ์ฅํ๋ผ."], | |
["down/์๋ฌ๋.png", None, None, "(#1์ ๋ฌ๋)์ ๋ด์ ์ฉ๊ธฐ๋ ๋ฒ๋ฆฌ๊ณ ๋๊ณ ํฐ ์์ ์ ์์ (#1์ ๋ฌ๋)์์๋ง ๊ฐ๋ ์ฑ์์ ์์ ์ ์ธ ๊ฐ๋๋ก ์ด์ธ๋ฆฌ๋ ์ํ๊ณผ ํจ๊ป ํ๋ ์ดํ ํ ๋ชจ์ต์ ์ด๋ฏธ์ง๋ก ์์ฑํ๋ผ. "], | |
["down/์๋ฌ๋.png", "down/ํ๋ ์ดํ .png", None, "(#2 ํ๋ ์ดํ ํ ์ด๋ฏธ์ง)์ ๋ด๊ธด ์์์ (#1 ์๋ฌ๋)๋ก ๋ฐ๊พธ๊ณ ๋๋จธ์ง๋ ์๋๋ฅผ ์ ์งํ ์ฒด ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๋ผ."], | |
["down/์ปต.png", None, None, "(#1์ปต)์ ๋ธ๊ธฐ, ๋ฐ๋๋ผ, ์ด์ฝ ์์ด์คํฌ๋ฆผ์ ๋ด๊ณ ๊ทธ ์์ ์ด์ฝ ์๋ฝ์ด ํ๋ฅด๊ฒ ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๋ผ."] | |
] | |
# ๋ชจ๋ ์์ ๋ฅผ ํ ํ์ด์ง์ ํ์ํ๋๋ก ์์ ๋ ๋ถ๋ถ | |
gr.Examples( | |
examples=examples, | |
inputs=[image1_input, image2_input, image3_input, prompt_input], | |
examples_per_page=len(examples) # ๋ชจ๋ ์์ ๋ฅผ ํ ํ์ด์ง์ ํ์ | |
) | |
# ์ด๋ฏธ์ง ํธ์ง๊ธฐ ํญ ์ถ๊ฐ | |
with gr.Tab("๐จ ์ด๋ฏธ์ง ํธ์ง๊ธฐ", elem_classes="tab-content"): | |
# ์ฌ์ฉ ๊ฐ์ด๋ ์น์ ์ ๊ฑฐ | |
# gr.HTML(image_editor_guide_html) | |
with gr.Row(): | |
# ์ผ์ชฝ ์ด: ๋น์จ 1 | |
with gr.Column(scale=1): | |
# ======== ์ด๋ฏธ์ง ์ ๋ก๋ ์น์ ======== | |
with gr.Group(): | |
gr.HTML('<div class="section-title"><i class="fas fa-upload"></i> <span>์ด๋ฏธ์ง ์ ๋ก๋</span></div>') | |
edit_input_image = gr.Image(type="pil", label="ํธ์งํ ์ด๋ฏธ์ง", elem_classes="image-container") | |
# ======== ์ด๋ฏธ์ง ์กฐ์ ์น์ ======== | |
with gr.Group(): | |
gr.HTML('<div class="section-title"><i class="fas fa-sliders-h"></i> <span>์ด๋ฏธ์ง ์กฐ์ </span></div>') | |
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="๊ทธ๋ฆผ์ ์กฐ์ ") | |
# ์ค๋ฅธ์ชฝ ์ด: ๋น์จ 1 | |
with gr.Column(scale=1): | |
# ======== ํธ์ง๋ ์ด๋ฏธ์ง ์น์ ======== | |
with gr.Group(): | |
gr.HTML('<div class="section-title"><i class="fas fa-images"></i> <span>ํธ์ง๋ ์ด๋ฏธ์ง</span></div>') | |
edit_output_image = gr.Image(type="pil", label="ํธ์ง๋ ์ด๋ฏธ์ง", elem_classes="image-container") | |
# ======== ์ ์ฅ ์น์ ======== | |
with gr.Group(): | |
gr.HTML('<div class="section-title"><i class="fas fa-download"></i> <span>์ ์ฅ</span></div>') | |
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") | |
# ========== ์ด๋ฏธ์ง ์์ฑ๊ธฐ ์ด๋ฒคํธ ์ฐ๊ฒฐ ========== | |
# ๋ฒํผ ์ด๋ฒคํธ ์ฐ๊ฒฐ | |
image_change_btn1.click( | |
fn=lambda: "(#1์ ์ฌ์ฑ)์ด ์ด์ง ๋ค๋ก ๋์๋ณด๋ ๋ชจ์ต์ผ๋ก ์ต๋ํ ์ด์ seed๋ฅผ ์ ์งํํ ์์ฐ์ค๋ฝ๊ฒ ๋ณ๊ฒฝํ๋ผ.", | |
inputs=[], | |
outputs=prompt_input | |
) | |
image_change_btn2.click( | |
fn=lambda: "(#1 ๋ ๋ชจ๋ชจํ)์์ ์ฒญ์์์ด๋ ๊ณ ๋ง ๊ฒ์์ ๊ณ ๋๋ ๊ณ ๋ก ๋ณ๊ฒฝํ๊ณ ๋๋จธ์ง ๋ถ๋ถ์ seed๋ฅผ ๋ณ๊ฒฝ์ ํ์ง๋ง๋ผ.", | |
inputs=[], | |
outputs=prompt_input | |
) | |
image_change_btn3.click( | |
fn=lambda: "(#1 ์ฌํ์ฉ ์ผ์๋ฐ์ค)์์ ์ผ์์ด ๋ด๊ธด 3์์ ์ฝ๋ผ๊ฐ ๋์ฌ์๋ ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๋ผ.", | |
inputs=[], | |
outputs=prompt_input | |
) | |
text_remove_btn.click( | |
fn=lambda: "(#1 ์ด๋ฏธ์ง)์ ์๋ ์ค๊ตญ์ด๋ฅผ ๋ชจ๋ ์ ๊ฑฐํ๋ผ.", | |
inputs=[], | |
outputs=prompt_input | |
) | |
text_change_btn.click( | |
fn=lambda: '(#1์ ํ ์คํธ)๋ฅผ ์คํ์ผ์ ์ ์งํ์ฒด ํ ์คํธ๋ง "Hello"๋ก ๋ฐ๊ฟ๋ผ', | |
inputs=[], | |
outputs=prompt_input | |
) | |
clothes_change_btn1.click( | |
fn=lambda: "(#1์ ์ฌ์ฑ๋ชจ๋ธ)์ด ์ ์ฒด ๋น์จ๊ณผ ํฌ์ฆ๋ ์ ์งํ ์ฒด (#2์ ์ ๊ธ๋ผ์ค)์ (#3์ ์ฒญ๋ฐ์ง)๋ฅผ ์ง์ ๋ชจ๋ธ์ด ์ฐฉ์ฉํ๊ฒ ์ฒ๋ผ ์์ฐ์ค๋ฝ๊ฒ ๋ชจ์ต์ ์์ฑํ๋ผ.", | |
inputs=[], | |
outputs=prompt_input | |
) | |
clothes_change_btn2.click( | |
fn=lambda: "(#1์ ์ฌ์ฑ๋ชจ๋ธ)์ด (#2์ ์ ๊ธ๋ผ์ค)์ ์ฐฉ์ฉํ๊ณ (#3์ ๋ท๋ฐฐ๊ฒฝ์ ์นดํ์ ์ฒด๊ฐ ๋ณด์ด๋ฉฐ) ์์์ ์์ ์๋ ๋ชจ์ต์ ์์ฑํ๋ผ.", | |
inputs=[], | |
outputs=prompt_input | |
) | |
holding_product_btn.click( | |
fn=lambda: "(#1์ ์ฌ์ฑ๋ชจ๋ธ)์ด(#2์ ์์ธ์)์ ๋ค๊ณ ์๋ ์์ฐ์ค๋ฌ์ด ๋ชจ์ต์ ์์ฑํ๋ผ.", | |
inputs=[], | |
outputs=prompt_input | |
) | |
background_change_btn.click( | |
fn=lambda: "(#1์ ์ฌ์ฑ๋ชจ๋ธ)์ด (#2 ์นดํ)์์ ์์ฐ์ค๋ฝ๊ฒ ์๋ ๋ชจ์ต์ ์์ฑํ๋ผ.", | |
inputs=[], | |
outputs=prompt_input | |
) | |
composite_product_btn.click( | |
fn=lambda: "(#1์ ๋ ๊ณ ๋ชจํ)์์ ์ฒญ์์์ด๋ ๊ณ ๋ฅผ ์ ๊ฑฐํ ํ, ๊ทธ ์๋ฆฌ๋ฅผ ์ฃผ๋ณ ๋ฐฐ๊ฒฝ๊ณผ ์์ฐ์ค๋ฝ๊ฒ ์ด์ฐ๋ฌ์ง๋๋ก ์ฑ์์ฃผ์ธ์. ๋จ, ์ด๋ฏธ์ง์ ๋ค๋ฅธ ๋ถ๋ถ์ ์ฃผ์ ์์๋ ๋์ผํ๊ฒ ์ ์ง ํด์ผํ๋ค.", | |
inputs=[], | |
outputs=prompt_input | |
) | |
outpainting_btn.click( | |
fn=lambda: "(#1 ์ด๋ฏธ์ง)๋ฅผ ์๋ณธ๊ทธ๋๋ก ์ค์์ ๋๊ณ ๋น์จ๋ก ์ ์งํ ์ฒด ์์๋ ๋ฐ ์ข์ฐ๋ก ํฌ๊ฒ ํ์ฅํ๋ผ.", | |
inputs=[], | |
outputs=prompt_input | |
) | |
food_btn_1.click( | |
fn=lambda: "(#1์ ๋ฌ๋)์ ๋ด์ ์ฉ๊ธฐ๋ ๋ฒ๋ฆฌ๊ณ ๋๊ณ ํฐ ์์ ์ ์์ (#1์ ๋ฌ๋)์์๋ง ๊ฐ๋ ์ฑ์์ ์์ ์ ์ธ ๊ฐ๋๋ก ์ด์ธ๋ฆฌ๋ ์ํ๊ณผ ํจ๊ป ํ๋ ์ดํ ํ ๋ชจ์ต์ ์ด๋ฏธ์ง๋ก ์์ฑํ๋ผ. ", | |
inputs=[], | |
outputs=prompt_input | |
) | |
food_btn_2.click( | |
fn=lambda: "(#2 ํ๋ ์ดํ ํ ์ด๋ฏธ์ง)์ ๋ด๊ธด ์์์ (#1 ์๋ฌ๋)๋ก ๋ฐ๊พธ๊ณ ๋๋จธ์ง๋ ์๋๋ฅผ ์ ์งํ ์ฒด ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๋ผ.", | |
inputs=[], | |
outputs=prompt_input | |
) | |
food_btn_3.click( | |
fn=lambda: "(#1์ปต)์ ๋ธ๊ธฐ, ๋ฐ๋๋ผ, ์ด์ฝ ์์ด์คํฌ๋ฆผ์ ๋ด๊ณ ๊ทธ ์์ ์ด์ฝ ์๋ฝ์ด ํ๋ฅด๊ฒ ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๋ผ.", | |
inputs=[], | |
outputs=prompt_input | |
) | |
# ๋จ์ผ ์ด๋ฏธ์ง ์์ฑ ๋ฒํผ ์ด๋ฒคํธ ์ฐ๊ฒฐ - API ํค ์ ๋ ฅ๊ฐ ์ ๊ฑฐ | |
def generate_single_image(image1, image2, image3, prompt): | |
return process_images_with_prompt(image1, image2, image3, prompt, 0, 3) | |
submit_single_btn.click( | |
fn=generate_single_image, | |
inputs=[image1_input, image2_input, image3_input, prompt_input], | |
outputs=[output_image1, output_text, prompt_display], | |
) | |
# 4์ฅ ์ด๋ฏธ์ง ์์ฑ ๋ฒํผ ์ด๋ฒคํธ ์ฐ๊ฒฐ - API ํค ์ ๋ ฅ๊ฐ ์ ๊ฑฐ | |
submit_btn.click( | |
fn=generate_multiple_images, | |
inputs=[image1_input, image2_input, image3_input, prompt_input], | |
outputs=[output_image1, output_image2, output_image3, output_image4, output_text, prompt_display], | |
) | |
# ========== ์ด๋ฏธ์ง ํธ์ง๊ธฐ ์ด๋ฒคํธ ์ฐ๊ฒฐ ========== | |
# ์ด๋ฏธ์ง ์ฒ๋ฆฌ ํจ์ ์ฐ๊ฒฐ | |
edit_inputs = [ | |
edit_input_image, | |
brightness_slider, | |
contrast_slider, | |
saturation_slider, | |
temperature_slider, | |
tint_slider, | |
exposure_slider, | |
vibrance_slider, | |
color_mixer_blues_slider, | |
shadows_slider | |
] | |
edit_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 edit_input_components: | |
input_component.change( | |
fn=process_image, | |
inputs=edit_inputs, | |
outputs=edit_output_image | |
) | |
# ์ด๋ฏธ์ง ์ ๋ก๋ ์ ์๋์ผ๋ก ํธ์ง ์ ๋ฐ์ดํธ | |
edit_input_image.change( | |
fn=process_image, | |
inputs=edit_inputs, | |
outputs=edit_output_image | |
) | |
# ๋ค์ด๋ก๋ ๋ฒํผ ๊ธฐ๋ฅ | |
download_button.click( | |
fn=download_edited_image, | |
inputs=[edit_output_image, edit_input_image], | |
outputs=download_output | |
) | |
# API ํค ์ด๊ธฐํ ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์คํ | |
initialize_api_keys() # API ํค ์ด๊ธฐํ ํจ์ ํธ์ถ | |
demo.queue() | |
demo.launch(share=False, inbrowser=True, width="100%") # width ํ๋ผ๋ฏธํฐ ์ถ๊ฐ |