Spaces:
Sleeping
Sleeping
| 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 | |
| ) |