Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
@@ -87,27 +87,105 @@ def auto_crop_preview(image):
|
|
87 |
|
88 |
return preview, f"建議裁切區域:{crop_box}"
|
89 |
|
90 |
-
def
|
91 |
"""
|
92 |
-
|
93 |
"""
|
94 |
if image is None:
|
95 |
-
return None, "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
96 |
|
97 |
-
|
98 |
-
|
|
|
|
|
|
|
99 |
|
100 |
-
#
|
101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
|
103 |
-
|
104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
|
106 |
# 調整為目標尺寸
|
107 |
target_size = (1170, 391)
|
108 |
final_image = cropped.resize(target_size, Image.Resampling.LANCZOS)
|
109 |
|
110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
|
112 |
# 創建 Gradio 界面
|
113 |
with gr.Blocks(title="圖片裁切工具", theme=gr.themes.Soft()) as demo:
|
@@ -116,30 +194,30 @@ with gr.Blocks(title="圖片裁切工具", theme=gr.themes.Soft()) as demo:
|
|
116 |
|
117 |
with gr.Row():
|
118 |
with gr.Column(scale=1):
|
119 |
-
#
|
120 |
input_image = gr.Image(
|
121 |
-
label="
|
122 |
type="pil",
|
123 |
height=400,
|
124 |
-
crop_size="1170:391",
|
125 |
interactive=True
|
126 |
)
|
127 |
|
128 |
# 手動裁切圖片區域
|
129 |
crop_image = gr.Image(
|
130 |
-
label="
|
131 |
type="pil",
|
132 |
height=400,
|
133 |
-
tool="select",
|
134 |
interactive=True,
|
135 |
visible=False
|
136 |
)
|
137 |
|
138 |
# 按鈕區域
|
139 |
with gr.Row():
|
140 |
-
preview_btn = gr.Button("🔍
|
141 |
-
|
142 |
-
|
|
|
|
|
143 |
reset_btn = gr.Button("🔄 重置", variant="secondary")
|
144 |
|
145 |
with gr.Column(scale=1):
|
@@ -174,52 +252,71 @@ with gr.Blocks(title="圖片裁切工具", theme=gr.themes.Soft()) as demo:
|
|
174 |
visible=False
|
175 |
)
|
176 |
|
177 |
-
# 進階設置
|
178 |
-
with gr.Accordion("🔧 進階設置", open=False):
|
179 |
-
gr.Markdown("### 目標尺寸設置")
|
180 |
-
with gr.Row():
|
181 |
-
target_width = gr.Number(
|
182 |
-
label="目標寬度",
|
183 |
-
value=1170,
|
184 |
-
precision=0
|
185 |
-
)
|
186 |
-
target_height = gr.Number(
|
187 |
-
label="目標高度",
|
188 |
-
value=391,
|
189 |
-
precision=0
|
190 |
-
)
|
191 |
-
|
192 |
-
quality_slider = gr.Slider(
|
193 |
-
label="輸出品質",
|
194 |
-
minimum=1,
|
195 |
-
maximum=100,
|
196 |
-
value=95,
|
197 |
-
step=1
|
198 |
-
)
|
199 |
-
|
200 |
# 使用說明
|
201 |
with gr.Accordion("📖 使用說明", open=False):
|
202 |
gr.Markdown("""
|
203 |
### 如何使用:
|
204 |
1. **上傳圖片**:點擊上傳區域選擇您的圖片
|
205 |
-
2.
|
206 |
-
3.
|
207 |
-
|
208 |
-
|
|
|
209 |
|
210 |
### 功能特色:
|
211 |
-
- ✨ 自動計算最佳裁切比例
|
212 |
-
- 🎯
|
213 |
-
- 📏
|
214 |
- 🖼️ 輸出高品質 JPG 格式圖片
|
215 |
- 💾 簡單的一鍵下載功能
|
216 |
- 🔄 重置功能方便重新開始
|
|
|
|
|
|
|
|
|
|
|
217 |
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
218 |
|
219 |
|
220 |
# 事件綁定
|
|
|
|
|
221 |
preview_btn.click(
|
222 |
-
fn=
|
223 |
inputs=[input_image],
|
224 |
outputs=[preview_image, status_text]
|
225 |
).then(
|
@@ -227,45 +324,97 @@ with gr.Blocks(title="圖片裁切工具", theme=gr.themes.Soft()) as demo:
|
|
227 |
outputs=[preview_image]
|
228 |
)
|
229 |
|
230 |
-
|
231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
232 |
inputs=[input_image],
|
233 |
-
outputs=[
|
234 |
)
|
235 |
|
236 |
-
|
237 |
-
|
238 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
239 |
)
|
240 |
|
241 |
-
|
|
|
242 |
if image is None:
|
243 |
-
return None, "
|
244 |
|
245 |
-
result_path, status =
|
246 |
if result_path:
|
247 |
return result_path, status, gr.update(visible=True, value=result_path)
|
248 |
else:
|
249 |
return None, status, gr.update(visible=False)
|
250 |
|
251 |
-
|
252 |
-
fn=
|
253 |
-
inputs=[input_image],
|
254 |
outputs=[output_image, status_text, download_btn]
|
255 |
)
|
256 |
|
257 |
-
#
|
258 |
-
|
259 |
-
fn=
|
260 |
-
|
261 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
262 |
)
|
263 |
|
264 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
265 |
input_image.change(
|
266 |
-
fn=
|
267 |
inputs=[input_image],
|
268 |
-
outputs=[status_text]
|
269 |
)
|
270 |
|
271 |
# 啟動應用程式
|
|
|
87 |
|
88 |
return preview, f"建議裁切區域:{crop_box}"
|
89 |
|
90 |
+
def create_crop_interface(image):
|
91 |
"""
|
92 |
+
創建裁切界面,顯示帶有裁切框的圖片
|
93 |
"""
|
94 |
if image is None:
|
95 |
+
return None, "請先上傳圖片"
|
96 |
+
|
97 |
+
# 計算建議的裁切區域
|
98 |
+
crop_box = calculate_crop_box(image)
|
99 |
+
left, top, right, bottom = crop_box
|
100 |
+
|
101 |
+
# 創建帶有裁切框的預覽圖
|
102 |
+
preview = image.copy()
|
103 |
+
from PIL import ImageDraw, ImageFont
|
104 |
+
draw = ImageDraw.Draw(preview)
|
105 |
+
|
106 |
+
# 繪製裁切框
|
107 |
+
draw.rectangle(crop_box, outline="red", width=5)
|
108 |
|
109 |
+
# 添加半透明遮罩到裁切區域外
|
110 |
+
overlay = Image.new('RGBA', image.size, (0, 0, 0, 100))
|
111 |
+
mask = Image.new('RGBA', image.size, (0, 0, 0, 0))
|
112 |
+
mask_draw = ImageDraw.Draw(mask)
|
113 |
+
mask_draw.rectangle(crop_box, fill=(255, 255, 255, 255))
|
114 |
|
115 |
+
# 創建最終預覽
|
116 |
+
preview = Image.alpha_composite(preview.convert('RGBA'), overlay)
|
117 |
+
preview = Image.alpha_composite(preview, Image.new('RGBA', image.size, (0, 0, 0, 0)))
|
118 |
+
|
119 |
+
return preview.convert('RGB'), f"建議裁切區域:{crop_box}\n可以點擊「使用建議區域」或「自定義裁切」"
|
120 |
+
|
121 |
+
def custom_crop_with_coordinates(image, x, y, width, height):
|
122 |
+
"""
|
123 |
+
使用自定義座標裁切圖片
|
124 |
+
"""
|
125 |
+
if image is None:
|
126 |
+
return None, "請先上傳圖片"
|
127 |
|
128 |
+
try:
|
129 |
+
# 確保座標在圖片範圍內
|
130 |
+
img_width, img_height = image.size
|
131 |
+
x = max(0, min(x, img_width))
|
132 |
+
y = max(0, min(y, img_height))
|
133 |
+
width = max(10, min(width, img_width - x))
|
134 |
+
height = max(10, min(height, img_height - y))
|
135 |
+
|
136 |
+
# 執行裁切
|
137 |
+
cropped = image.crop((x, y, x + width, y + height))
|
138 |
+
|
139 |
+
# 調整為目標尺寸
|
140 |
+
target_size = (1170, 391)
|
141 |
+
final_image = cropped.resize(target_size, Image.Resampling.LANCZOS)
|
142 |
+
|
143 |
+
# 轉換為 RGB 並保存為 JPG
|
144 |
+
if final_image.mode in ('RGBA', 'LA', 'P'):
|
145 |
+
rgb_image = Image.new('RGB', final_image.size, (255, 255, 255))
|
146 |
+
if final_image.mode == 'P':
|
147 |
+
final_image = final_image.convert('RGBA')
|
148 |
+
rgb_image.paste(final_image, mask=final_image.split()[-1] if final_image.mode == 'RGBA' else None)
|
149 |
+
final_image = rgb_image
|
150 |
+
|
151 |
+
# 保存為 JPG 檔案
|
152 |
+
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.jpg')
|
153 |
+
final_image.save(temp_file.name, format='JPEG', quality=95, optimize=True)
|
154 |
+
temp_file.close()
|
155 |
+
|
156 |
+
return temp_file.name, f"自定義裁切完成!裁切區域:({x}, {y}, {width}, {height})"
|
157 |
+
|
158 |
+
except Exception as e:
|
159 |
+
return None, f"裁切失敗:{str(e)}"
|
160 |
+
|
161 |
+
def use_suggested_crop(image):
|
162 |
+
"""
|
163 |
+
使用建議的裁切區域
|
164 |
+
"""
|
165 |
+
if image is None:
|
166 |
+
return None, "請先上傳圖片"
|
167 |
+
|
168 |
+
crop_box = calculate_crop_box(image)
|
169 |
+
cropped = image.crop(crop_box)
|
170 |
|
171 |
# 調整為目標尺寸
|
172 |
target_size = (1170, 391)
|
173 |
final_image = cropped.resize(target_size, Image.Resampling.LANCZOS)
|
174 |
|
175 |
+
# 轉換為 RGB 並保存為 JPG
|
176 |
+
if final_image.mode in ('RGBA', 'LA', 'P'):
|
177 |
+
rgb_image = Image.new('RGB', final_image.size, (255, 255, 255))
|
178 |
+
if final_image.mode == 'P':
|
179 |
+
final_image = final_image.convert('RGBA')
|
180 |
+
rgb_image.paste(final_image, mask=final_image.split()[-1] if final_image.mode == 'RGBA' else None)
|
181 |
+
final_image = rgb_image
|
182 |
+
|
183 |
+
# 保存為 JPG 檔案
|
184 |
+
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.jpg')
|
185 |
+
final_image.save(temp_file.name, format='JPEG', quality=95, optimize=True)
|
186 |
+
temp_file.close()
|
187 |
+
|
188 |
+
return temp_file.name, f"使用建議裁切區域完成!區域:{crop_box}"
|
189 |
|
190 |
# 創建 Gradio 界面
|
191 |
with gr.Blocks(title="圖片裁切工具", theme=gr.themes.Soft()) as demo:
|
|
|
194 |
|
195 |
with gr.Row():
|
196 |
with gr.Column(scale=1):
|
197 |
+
# 圖片上傳區域
|
198 |
input_image = gr.Image(
|
199 |
+
label="上傳圖片",
|
200 |
type="pil",
|
201 |
height=400,
|
|
|
202 |
interactive=True
|
203 |
)
|
204 |
|
205 |
# 手動裁切圖片區域
|
206 |
crop_image = gr.Image(
|
207 |
+
label="手動裁切區域(點擊並拖拽選擇裁切區域)",
|
208 |
type="pil",
|
209 |
height=400,
|
|
|
210 |
interactive=True,
|
211 |
visible=False
|
212 |
)
|
213 |
|
214 |
# 按鈕區域
|
215 |
with gr.Row():
|
216 |
+
preview_btn = gr.Button("🔍 顯示建議裁切區域", variant="secondary")
|
217 |
+
suggest_btn = gr.Button("✅ 使用建議區域", variant="primary")
|
218 |
+
|
219 |
+
with gr.Row():
|
220 |
+
edit_btn = gr.Button("✏️ 自定義裁切", variant="secondary")
|
221 |
reset_btn = gr.Button("🔄 重置", variant="secondary")
|
222 |
|
223 |
with gr.Column(scale=1):
|
|
|
252 |
visible=False
|
253 |
)
|
254 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
255 |
# 使用說明
|
256 |
with gr.Accordion("📖 使用說明", open=False):
|
257 |
gr.Markdown("""
|
258 |
### 如何使用:
|
259 |
1. **上傳圖片**:點擊上傳區域選擇您的圖片
|
260 |
+
2. **查看建議**:點擊「顯示建議裁切區域」查看系統建議
|
261 |
+
3. **選擇方式**:
|
262 |
+
- 點擊「使用建議區域」直接使用系統建議
|
263 |
+
- 點擊「自定義裁切」手動輸入座標和尺寸
|
264 |
+
4. **下載結果**:點擊「下載 JPG 圖片」按鈕下載處理後的圖片
|
265 |
|
266 |
### 功能特色:
|
267 |
+
- ✨ 自動計算最佳裁切比例 (1170:391)
|
268 |
+
- 🎯 支援自定義座標裁切
|
269 |
+
- 📏 保持目標比例的精確輸出
|
270 |
- 🖼️ 輸出高品質 JPG 格式圖片
|
271 |
- 💾 簡單的一鍵下載功能
|
272 |
- 🔄 重置功能方便重新開始
|
273 |
+
|
274 |
+
### 自定義裁切說明:
|
275 |
+
- **X, Y 座標**:裁切區域的左上角位置
|
276 |
+
- **寬度, 高度**:裁切區域的大小
|
277 |
+
- 最終輸出會調整為 1170×391 像素
|
278 |
""")
|
279 |
+
|
280 |
+
# 自定義裁切參數區域
|
281 |
+
with gr.Row(visible=False) as custom_crop_row:
|
282 |
+
gr.Markdown("### 🎯 自定義裁切參數")
|
283 |
+
|
284 |
+
with gr.Row(visible=False) as crop_controls:
|
285 |
+
with gr.Column():
|
286 |
+
crop_x = gr.Number(
|
287 |
+
label="X 座標 (左上角)",
|
288 |
+
value=0,
|
289 |
+
precision=0,
|
290 |
+
minimum=0
|
291 |
+
)
|
292 |
+
crop_y = gr.Number(
|
293 |
+
label="Y 座標 (左上角)",
|
294 |
+
value=0,
|
295 |
+
precision=0,
|
296 |
+
minimum=0
|
297 |
+
)
|
298 |
+
with gr.Column():
|
299 |
+
crop_width = gr.Number(
|
300 |
+
label="寬度",
|
301 |
+
value=300,
|
302 |
+
precision=0,
|
303 |
+
minimum=10
|
304 |
+
)
|
305 |
+
crop_height = gr.Number(
|
306 |
+
label="高度",
|
307 |
+
value=100,
|
308 |
+
precision=0,
|
309 |
+
minimum=10
|
310 |
+
)
|
311 |
+
with gr.Column():
|
312 |
+
apply_crop_btn = gr.Button("🎯 套用自定義裁切", variant="primary")
|
313 |
|
314 |
|
315 |
# 事件綁定
|
316 |
+
|
317 |
+
# 顯示建議裁切區域
|
318 |
preview_btn.click(
|
319 |
+
fn=create_crop_interface,
|
320 |
inputs=[input_image],
|
321 |
outputs=[preview_image, status_text]
|
322 |
).then(
|
|
|
324 |
outputs=[preview_image]
|
325 |
)
|
326 |
|
327 |
+
# 使用建議區域直接處理
|
328 |
+
def suggest_and_download(image):
|
329 |
+
if image is None:
|
330 |
+
return None, "請先上傳圖片", gr.update(visible=False)
|
331 |
+
|
332 |
+
result_path, status = use_suggested_crop(image)
|
333 |
+
if result_path:
|
334 |
+
return result_path, status, gr.update(visible=True, value=result_path)
|
335 |
+
else:
|
336 |
+
return None, status, gr.update(visible=False)
|
337 |
+
|
338 |
+
suggest_btn.click(
|
339 |
+
fn=suggest_and_download,
|
340 |
inputs=[input_image],
|
341 |
+
outputs=[output_image, status_text, download_btn]
|
342 |
)
|
343 |
|
344 |
+
# 顯示/隱藏自定義裁切控制項
|
345 |
+
def toggle_custom_controls(image):
|
346 |
+
if image is None:
|
347 |
+
return gr.update(visible=False), gr.update(visible=False)
|
348 |
+
|
349 |
+
# 獲取圖片尺寸以設定預設值
|
350 |
+
width, height = image.size
|
351 |
+
suggested_crop = calculate_crop_box(image)
|
352 |
+
|
353 |
+
return (
|
354 |
+
gr.update(visible=True),
|
355 |
+
gr.update(visible=True)
|
356 |
+
)
|
357 |
+
|
358 |
+
edit_btn.click(
|
359 |
+
fn=toggle_custom_controls,
|
360 |
+
inputs=[input_image],
|
361 |
+
outputs=[custom_crop_row, crop_controls]
|
362 |
)
|
363 |
|
364 |
+
# 套用自定義裁切
|
365 |
+
def apply_custom_crop(image, x, y, width, height):
|
366 |
if image is None:
|
367 |
+
return None, "請先上傳圖片", gr.update(visible=False)
|
368 |
|
369 |
+
result_path, status = custom_crop_with_coordinates(image, x, y, width, height)
|
370 |
if result_path:
|
371 |
return result_path, status, gr.update(visible=True, value=result_path)
|
372 |
else:
|
373 |
return None, status, gr.update(visible=False)
|
374 |
|
375 |
+
apply_crop_btn.click(
|
376 |
+
fn=apply_custom_crop,
|
377 |
+
inputs=[input_image, crop_x, crop_y, crop_width, crop_height],
|
378 |
outputs=[output_image, status_text, download_btn]
|
379 |
)
|
380 |
|
381 |
+
# 重置功能
|
382 |
+
reset_btn.click(
|
383 |
+
fn=lambda: (
|
384 |
+
None, None, None,
|
385 |
+
gr.update(visible=False),
|
386 |
+
gr.update(visible=False),
|
387 |
+
gr.update(visible=False),
|
388 |
+
gr.update(visible=False),
|
389 |
+
"請上傳圖片"
|
390 |
+
),
|
391 |
+
outputs=[
|
392 |
+
input_image, crop_image, output_image,
|
393 |
+
preview_image, custom_crop_row, crop_controls,
|
394 |
+
download_btn, status_text
|
395 |
+
]
|
396 |
)
|
397 |
|
398 |
+
# 當圖片上傳時自動更新狀態和建議值
|
399 |
+
def update_on_upload(image):
|
400 |
+
if image is None:
|
401 |
+
return "請上傳圖片", 0, 0, 300, 100
|
402 |
+
|
403 |
+
# 計算建議的裁切區域
|
404 |
+
crop_box = calculate_crop_box(image)
|
405 |
+
left, top, right, bottom = crop_box
|
406 |
+
width = right - left
|
407 |
+
height = bottom - top
|
408 |
+
|
409 |
+
return (
|
410 |
+
f"圖片已上傳!尺寸:{image.size[0]}×{image.size[1]}",
|
411 |
+
left, top, width, height
|
412 |
+
)
|
413 |
+
|
414 |
input_image.change(
|
415 |
+
fn=update_on_upload,
|
416 |
inputs=[input_image],
|
417 |
+
outputs=[status_text, crop_x, crop_y, crop_width, crop_height]
|
418 |
)
|
419 |
|
420 |
# 啟動應用程式
|