ImageCorpForAD / app.py
dseditor's picture
Upload app.py
8cbf42a verified
import gradio as gr
import numpy as np
from PIL import Image
import io
import base64
import tempfile
import os
def calculate_crop_box(image, target_ratio=1170/391):
"""
計算保持目標比例的最大裁切框
"""
width, height = image.size
current_ratio = width / height
if current_ratio > target_ratio:
# 圖片較寬,需要裁切寬度
new_width = int(height * target_ratio)
left = (width - new_width) // 2
top = 0
right = left + new_width
bottom = height
else:
# 圖片較高,需要裁切高度
new_height = int(width / target_ratio)
left = 0
top = (height - new_height) // 2
right = width
bottom = top + new_height
return (left, top, right, bottom)
def process_image(image, crop_data=None):
"""
處理圖片:裁切並調整為目標尺寸,並保存為 JPG 格式
"""
if image is None:
return None, "請上傳圖片"
# 如果有裁切數據,使用用戶選擇的區域
if crop_data is not None:
# Gradio 的 Image 組件在編輯模式下會返回包含裁切信息的數據
# 這裡我們處理裁切後的圖片
processed_image = image
else:
# 如果沒有裁切數據,自動計算最佳裁切區域
crop_box = calculate_crop_box(image)
processed_image = image.crop(crop_box)
# 調整為目標尺寸
target_size = (1170, 391)
final_image = processed_image.resize(target_size, Image.Resampling.LANCZOS)
# 轉換為 RGB 模式以確保 JPG 格式輸出
if final_image.mode in ('RGBA', 'LA', 'P'):
# 創建白色背景
rgb_image = Image.new('RGB', final_image.size, (255, 255, 255))
if final_image.mode == 'P':
final_image = final_image.convert('RGBA')
rgb_image.paste(final_image, mask=final_image.split()[-1] if final_image.mode == 'RGBA' else None)
final_image = rgb_image
# 保存為 JPG 格式的臨時檔案
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.jpg')
final_image.save(temp_file.name, format='JPEG', quality=95, optimize=True)
temp_file.close()
return temp_file.name, f"圖片已處理完成!尺寸:{final_image.size},格式:JPG"
def auto_crop_preview(image):
"""
自動裁切預覽
"""
if image is None:
return None, "請上傳圖片"
crop_box = calculate_crop_box(image)
cropped = image.crop(crop_box)
# 創建預覽圖片,顯示裁切區域
preview = image.copy()
from PIL import ImageDraw
draw = ImageDraw.Draw(preview)
# 繪製裁切框
draw.rectangle(crop_box, outline="red", width=3)
return preview, f"建議裁切區域:{crop_box}"
def create_crop_interface(image):
"""
創建裁切界面,顯示帶有裁切框的圖片
"""
if image is None:
return None, "請先上傳圖片"
# 計算建議的裁切區域
crop_box = calculate_crop_box(image)
left, top, right, bottom = crop_box
# 創建帶有裁切框的預覽圖
preview = image.copy()
from PIL import ImageDraw, ImageFont
draw = ImageDraw.Draw(preview)
# 繪製裁切框
draw.rectangle(crop_box, outline="red", width=5)
# 添加半透明遮罩到裁切區域外
overlay = Image.new('RGBA', image.size, (0, 0, 0, 100))
mask = Image.new('RGBA', image.size, (0, 0, 0, 0))
mask_draw = ImageDraw.Draw(mask)
mask_draw.rectangle(crop_box, fill=(255, 255, 255, 255))
# 創建最終預覽
preview = Image.alpha_composite(preview.convert('RGBA'), overlay)
preview = Image.alpha_composite(preview, Image.new('RGBA', image.size, (0, 0, 0, 0)))
return preview.convert('RGB'), f"建議裁切區域:{crop_box}\n可以點擊「使用建議區域」或「自定義裁切」"
def custom_crop_with_coordinates(image, x, y, width, height):
"""
使用自定義座標裁切圖片
"""
if image is None:
return None, "請先上傳圖片"
try:
# 確保座標在圖片範圍內
img_width, img_height = image.size
x = max(0, min(x, img_width))
y = max(0, min(y, img_height))
width = max(10, min(width, img_width - x))
height = max(10, min(height, img_height - y))
# 執行裁切
cropped = image.crop((x, y, x + width, y + height))
# 調整為目標尺寸
target_size = (1170, 391)
final_image = cropped.resize(target_size, Image.Resampling.LANCZOS)
# 轉換為 RGB 並保存為 JPG
if final_image.mode in ('RGBA', 'LA', 'P'):
rgb_image = Image.new('RGB', final_image.size, (255, 255, 255))
if final_image.mode == 'P':
final_image = final_image.convert('RGBA')
rgb_image.paste(final_image, mask=final_image.split()[-1] if final_image.mode == 'RGBA' else None)
final_image = rgb_image
# 保存為 JPG 檔案
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.jpg')
final_image.save(temp_file.name, format='JPEG', quality=95, optimize=True)
temp_file.close()
return temp_file.name, f"自定義裁切完成!裁切區域:({x}, {y}, {width}, {height})"
except Exception as e:
return None, f"裁切失敗:{str(e)}"
def use_suggested_crop(image):
"""
使用建議的裁切區域
"""
if image is None:
return None, "請先上傳圖片"
crop_box = calculate_crop_box(image)
cropped = image.crop(crop_box)
# 調整為目標尺寸
target_size = (1170, 391)
final_image = cropped.resize(target_size, Image.Resampling.LANCZOS)
# 轉換為 RGB 並保存為 JPG
if final_image.mode in ('RGBA', 'LA', 'P'):
rgb_image = Image.new('RGB', final_image.size, (255, 255, 255))
if final_image.mode == 'P':
final_image = final_image.convert('RGBA')
rgb_image.paste(final_image, mask=final_image.split()[-1] if final_image.mode == 'RGBA' else None)
final_image = rgb_image
# 保存為 JPG 檔案
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.jpg')
final_image.save(temp_file.name, format='JPEG', quality=95, optimize=True)
temp_file.close()
return temp_file.name, f"使用建議裁切區域完成!區域:{crop_box}"
# 創建 Gradio 界面
with gr.Blocks(title="圖片裁切工具", theme=gr.themes.Soft()) as demo:
gr.Markdown("# 🖼️ 圖片裁切工具")
gr.Markdown("### 將任意尺寸的圖片按 1170:391 比例裁切並調整為目標尺寸")
with gr.Row():
with gr.Column(scale=1):
# 圖片上傳區域
input_image = gr.Image(
label="上傳圖片",
type="pil",
height=400,
interactive=True
)
# 手動裁切圖片區域
crop_image = gr.Image(
label="手動裁切區域(點擊並拖拽選擇裁切區域)",
type="pil",
height=400,
interactive=True,
visible=False
)
# 按鈕區域
with gr.Row():
preview_btn = gr.Button("🔍 顯示建議裁切區域", variant="secondary")
suggest_btn = gr.Button("✅ 使用建議區域", variant="primary")
with gr.Row():
edit_btn = gr.Button("✏️ 自定義裁切", variant="secondary")
reset_btn = gr.Button("🔄 重置", variant="secondary")
with gr.Column(scale=1):
# 輸出區域
output_image = gr.Image(
label="處理後圖片 (JPG格式)",
type="filepath",
height=400,
interactive=False
)
# 下載按鈕
download_btn = gr.DownloadButton(
label="📥 下載 JPG 圖片",
variant="primary",
visible=False
)
# 狀態信息
status_text = gr.Textbox(
label="狀態",
value="等待圖片上傳...",
interactive=False
)
# 預覽功能
with gr.Row():
preview_image = gr.Image(
label="裁切區域預覽(紅框顯示建議裁切區域)",
type="pil",
height=300,
visible=False
)
# 使用說明
with gr.Accordion("📖 使用說明", open=False):
gr.Markdown("""
### 如何使用:
1. **上傳圖片**:點擊上傳區域選擇您的圖片
2. **查看建議**:點擊「顯示建議裁切區域」查看系統建議
3. **選擇方式**:
- 點擊「使用建議區域」直接使用系統建議
- 點擊「自定義裁切」手動輸入座標和尺寸
4. **下載結果**:點擊「下載 JPG 圖片」按鈕下載處理後的圖片
### 功能特色:
- ✨ 自動計算最佳裁切比例 (1170:391)
- 🎯 支援自定義座標裁切
- 📏 保持目標比例的精確輸出
- 🖼️ 輸出高品質 JPG 格式圖片
- 💾 簡單的一鍵下載功能
- 🔄 重置功能方便重新開始
### 自定義裁切說明:
- **X, Y 座標**:裁切區域的左上角位置
- **寬度, 高度**:裁切區域的大小
- 最終輸出會調整為 1170×391 像素
""")
# 自定義裁切參數區域
with gr.Row(visible=False) as custom_crop_row:
gr.Markdown("### 🎯 自定義裁切參數")
with gr.Row(visible=False) as crop_controls:
with gr.Column():
crop_x = gr.Number(
label="X 座標 (左上角)",
value=0,
precision=0,
minimum=0
)
crop_y = gr.Number(
label="Y 座標 (左上角)",
value=0,
precision=0,
minimum=0
)
with gr.Column():
crop_width = gr.Number(
label="寬度",
value=300,
precision=0,
minimum=10
)
crop_height = gr.Number(
label="高度",
value=100,
precision=0,
minimum=10
)
with gr.Column():
apply_crop_btn = gr.Button("🎯 套用自定義裁切", variant="primary")
# 事件綁定
# 顯示建議裁切區域
preview_btn.click(
fn=create_crop_interface,
inputs=[input_image],
outputs=[preview_image, status_text]
).then(
fn=lambda: gr.update(visible=True),
outputs=[preview_image]
)
# 使用建議區域直接處理
def suggest_and_download(image):
if image is None:
return None, "請先上傳圖片", gr.update(visible=False)
result_path, status = use_suggested_crop(image)
if result_path:
return result_path, status, gr.update(visible=True, value=result_path)
else:
return None, status, gr.update(visible=False)
suggest_btn.click(
fn=suggest_and_download,
inputs=[input_image],
outputs=[output_image, status_text, download_btn]
)
# 顯示/隱藏自定義裁切控制項
def toggle_custom_controls(image):
if image is None:
return gr.update(visible=False), gr.update(visible=False)
# 獲取圖片尺寸以設定預設值
width, height = image.size
suggested_crop = calculate_crop_box(image)
return (
gr.update(visible=True),
gr.update(visible=True)
)
edit_btn.click(
fn=toggle_custom_controls,
inputs=[input_image],
outputs=[custom_crop_row, crop_controls]
)
# 套用自定義裁切
def apply_custom_crop(image, x, y, width, height):
if image is None:
return None, "請先上傳圖片", gr.update(visible=False)
result_path, status = custom_crop_with_coordinates(image, x, y, width, height)
if result_path:
return result_path, status, gr.update(visible=True, value=result_path)
else:
return None, status, gr.update(visible=False)
apply_crop_btn.click(
fn=apply_custom_crop,
inputs=[input_image, crop_x, crop_y, crop_width, crop_height],
outputs=[output_image, status_text, download_btn]
)
# 重置功能
reset_btn.click(
fn=lambda: (
None, None, None,
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False),
"請上傳圖片"
),
outputs=[
input_image, crop_image, output_image,
preview_image, custom_crop_row, crop_controls,
download_btn, status_text
]
)
# 當圖片上傳時自動更新狀態和建議值
def update_on_upload(image):
if image is None:
return "請上傳圖片", 0, 0, 300, 100
# 計算建議的裁切區域
crop_box = calculate_crop_box(image)
left, top, right, bottom = crop_box
width = right - left
height = bottom - top
return (
f"圖片已上傳!尺寸:{image.size[0]}×{image.size[1]}",
left, top, width, height
)
input_image.change(
fn=update_on_upload,
inputs=[input_image],
outputs=[status_text, crop_x, crop_y, crop_width, crop_height]
)
# 啟動應用程式
if __name__ == "__main__":
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=True
)