|
import cv2 |
|
import numpy as np |
|
import os |
|
import gradio as gr |
|
import gc |
|
import zipfile |
|
|
|
|
|
MAX_FILES = 250 |
|
BG_PATH = "background.jpg" |
|
FG_DIR = "foregrounds" |
|
OUTPUT_DIR = "output" |
|
ZIP_PATH = "output/results.zip" |
|
TARGET_WIDTH, TARGET_HEIGHT = 2133, 1200 |
|
POS1 = (1032, 0) |
|
POS2 = (4666, 0) |
|
|
|
|
|
def check_write_permission(path): |
|
if not os.path.exists(path): |
|
try: |
|
os.makedirs(path) |
|
except Exception as e: |
|
raise RuntimeError(f"❌ 無法建立目錄 '{path}':{e}") |
|
elif not os.access(path, os.W_OK): |
|
raise RuntimeError(f"❌ 目錄 '{path}' 存在,但沒有寫入權限!") |
|
|
|
check_write_permission(FG_DIR) |
|
check_write_permission(OUTPUT_DIR) |
|
|
|
|
|
def overlay_image(bg, fg, x, y): |
|
h, w = fg.shape[:2] |
|
bh, bw = bg.shape[:2] |
|
if x + w > bw or y + h > bh: |
|
raise ValueError(f"前景圖超出背景邊界:({x},{y},{w},{h}) > 背景({bw},{bh})") |
|
|
|
if fg.shape[2] == 4: |
|
alpha = fg[:, :, 3:] / 255.0 |
|
bg[y:y+h, x:x+w] = alpha * fg[:, :, :3] + (1 - alpha) * bg[y:y+h, x:x+w] |
|
else: |
|
bg[y:y+h, x:x+w] = fg[:, :, :3] |
|
return bg |
|
|
|
|
|
def zip_output_files(): |
|
with zipfile.ZipFile(ZIP_PATH, "w", zipfile.ZIP_DEFLATED) as zipf: |
|
for file in sorted(os.listdir(OUTPUT_DIR)): |
|
if file.endswith(".jpg"): |
|
zipf.write(os.path.join(OUTPUT_DIR, file), arcname=file) |
|
return ZIP_PATH |
|
|
|
|
|
def process_images(fg_files): |
|
try: |
|
progress = gr.Progress() |
|
progress(0, desc="準備中...") |
|
|
|
if len(fg_files) == 0: |
|
return [], None, "請至少上傳一張前景圖" |
|
if len(fg_files) > MAX_FILES: |
|
raise gr.Error(f"最多支援 {MAX_FILES} 張圖片") |
|
|
|
bg = cv2.imread(BG_PATH) |
|
if bg is None: |
|
raise gr.Error("無法讀取背景圖(請確認 background.jpg 是否存在)") |
|
|
|
|
|
for f in os.listdir(OUTPUT_DIR): |
|
if f.endswith(".jpg"): |
|
os.remove(os.path.join(OUTPUT_DIR, f)) |
|
|
|
results = [] |
|
for i, fg_file in enumerate(fg_files, 1): |
|
progress(i / len(fg_files), f"合成第 {i} 張圖...") |
|
|
|
try: |
|
with open(fg_file.name, 'rb') as f: |
|
fg_data = np.frombuffer(f.read(), np.uint8) |
|
fg = cv2.imdecode(fg_data, cv2.IMREAD_UNCHANGED) |
|
if fg is None: |
|
continue |
|
|
|
fg = cv2.resize(fg, (TARGET_WIDTH, TARGET_HEIGHT), interpolation=cv2.INTER_AREA) |
|
|
|
result = overlay_image(bg.copy(), fg, *POS1) |
|
result = overlay_image(result, fg, *POS2) |
|
|
|
output_path = f"{OUTPUT_DIR}/result_{i:03d}.jpg" |
|
cv2.imwrite(output_path, result) |
|
results.append((output_path, f"Result {i:03d}")) |
|
|
|
if i % 10 == 0: |
|
gc.collect() |
|
|
|
except Exception as e: |
|
print(f"處理 {fg_file.name} 失敗: {str(e)}") |
|
continue |
|
|
|
progress(1, "完成!") |
|
zip_file = zip_output_files() |
|
return results, zip_file, f"成功處理 {len(results)}/{len(fg_files)} 張圖片,已打包為 ZIP 可下載" |
|
|
|
except Exception as e: |
|
raise gr.Error(f"處理出錯:{str(e)}") |
|
|
|
|
|
with gr.Blocks() as demo: |
|
gr.Markdown("## 🖼️ 寬幅圖片合成工具(背景固定為 7872x1200)") |
|
|
|
fg_upload = gr.Files(label="上傳前景圖(JPG 或 PNG,自動套用背景)", file_types=[".jpg", ".png"]) |
|
btn_run = gr.Button("開始合成", variant="primary") |
|
|
|
with gr.Row(): |
|
gallery = gr.Gallery(label="合成結果預覽", columns=3, preview=True) |
|
zip_download = gr.File(label="下載結果 ZIP 檔") |
|
log_output = gr.Textbox(label="日誌", interactive=False) |
|
|
|
btn_run.click( |
|
fn=process_images, |
|
inputs=fg_upload, |
|
outputs=[gallery, zip_download, log_output], |
|
concurrency_limit=4 |
|
) |
|
|
|
if __name__ == "__main__": |
|
demo.launch() |