Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import numpy as np | |
| from PIL import Image | |
| import io | |
| import base64 | |
| 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): | |
| """ | |
| 處理圖片:裁切並調整為目標尺寸 | |
| """ | |
| 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) | |
| return final_image, f"圖片已處理完成!尺寸:{final_image.size}" | |
| 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}" | |
| # 創建 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 | |
| ) | |
| # 按鈕區域 | |
| with gr.Row(): | |
| preview_btn = gr.Button("🔍 預覽裁切區域", variant="secondary") | |
| process_btn = gr.Button("✂️ 裁切並處理", variant="primary") | |
| with gr.Column(scale=1): | |
| # 輸出區域 | |
| output_image = gr.Image( | |
| label="處理後圖片", | |
| type="pil", | |
| height=400 | |
| ) | |
| # 狀態信息 | |
| 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("### 目標尺寸設置") | |
| with gr.Row(): | |
| target_width = gr.Number( | |
| label="目標寬度", | |
| value=1170, | |
| precision=0 | |
| ) | |
| target_height = gr.Number( | |
| label="目標高度", | |
| value=391, | |
| precision=0 | |
| ) | |
| quality_slider = gr.Slider( | |
| label="輸出品質", | |
| minimum=1, | |
| maximum=100, | |
| value=95, | |
| step=1 | |
| ) | |
| # 使用說明 | |
| with gr.Accordion("📖 使用說明", open=False): | |
| gr.Markdown(""" | |
| ### 如何使用: | |
| 1. **上傳圖片**:點擊上傳區域選擇您的圖片 | |
| 2. **預覽裁切**:點擊「預覽裁切區域」查看建議的裁切區域 | |
| 3. **手動編輯**:在上傳的圖片上直接拖拽選擇裁切區域 | |
| 4. **處理圖片**:點擊「裁切並處理」生成最終圖片 | |
| 5. **下載結果**:右鍵點擊處理後的圖片選擇「另存為」 | |
| ### 功能特色: | |
| - ✨ 自動計算最佳裁切比例 | |
| - 🎯 支援手動選擇裁切區域 | |
| - 📏 保持 1170:391 的精確比例 | |
| - 🖼️ 高品質圖片輸出 | |
| - 💾 簡單的下載流程 | |
| """) | |
| # 事件綁定 | |
| preview_btn.click( | |
| fn=auto_crop_preview, | |
| inputs=[input_image], | |
| outputs=[preview_image, status_text] | |
| ).then( | |
| fn=lambda: gr.update(visible=True), | |
| outputs=[preview_image] | |
| ) | |
| process_btn.click( | |
| fn=process_image, | |
| inputs=[input_image], | |
| outputs=[output_image, status_text] | |
| ) | |
| # 當圖片上傳時自動更新狀態 | |
| input_image.change( | |
| fn=lambda img: "圖片已上傳,可以開始處理!" if img is not None else "請上傳圖片", | |
| inputs=[input_image], | |
| outputs=[status_text] | |
| ) | |
| # 啟動應用程式 | |
| if __name__ == "__main__": | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=True | |
| ) |