dseditor commited on
Commit
8cbf42a
·
verified ·
1 Parent(s): 34acaea

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +218 -69
app.py CHANGED
@@ -87,27 +87,105 @@ def auto_crop_preview(image):
87
 
88
  return preview, f"建議裁切區域:{crop_box}"
89
 
90
- def manual_crop_image(image, crop_coords):
91
  """
92
- 手動裁切圖片
93
  """
94
  if image is None:
95
- return None, "請上傳圖片"
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
- if crop_coords is None:
98
- return auto_crop_preview(image)
 
 
 
99
 
100
- # 解析裁切座標
101
- x, y, width, height = crop_coords
 
 
 
 
 
 
 
 
 
 
102
 
103
- # 執行裁切
104
- cropped = image.crop((x, y, x + width, y + height))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
  # 調整為目標尺寸
107
  target_size = (1170, 391)
108
  final_image = cropped.resize(target_size, Image.Resampling.LANCZOS)
109
 
110
- return final_image, f"手動裁切完成!裁切區域:({x}, {y}, {width}, {height})"
 
 
 
 
 
 
 
 
 
 
 
 
 
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("🔍 預覽建議區域", variant="secondary")
141
- edit_btn = gr.Button("✏️ 手動編輯", variant="secondary")
142
- process_btn = gr.Button("✂️ 裁切並處理", variant="primary")
 
 
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
- 4. **處理圖片**:點擊「裁切並處理」生成 JPG 格式的最終圖片
208
- 5. **下載結果**:點擊「下載 JPG 圖片」按鈕下載處理後的圖片
 
209
 
210
  ### 功能特色:
211
- - ✨ 自動計算最佳裁切比例
212
- - 🎯 支援手動拖拽選擇裁切區域
213
- - 📏 保持 1170:391 的精確比例
214
  - 🖼️ 輸出高品質 JPG 格式圖片
215
  - 💾 簡單的一鍵下載功能
216
  - 🔄 重置功能方便重新開始
 
 
 
 
 
217
  """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
 
220
  # 事件綁定
 
 
221
  preview_btn.click(
222
- fn=auto_crop_preview,
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
- edit_btn.click(
231
- fn=lambda img: (gr.update(visible=True, value=img), gr.update(visible=False)),
 
 
 
 
 
 
 
 
 
 
 
232
  inputs=[input_image],
233
- outputs=[crop_image, input_image]
234
  )
235
 
236
- reset_btn.click(
237
- fn=lambda: (gr.update(visible=True, value=None), gr.update(visible=False, value=None), gr.update(visible=False), None, "請上傳圖片"),
238
- outputs=[input_image, crop_image, preview_image, output_image, status_text]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  )
240
 
241
- def process_and_download(image):
 
242
  if image is None:
243
- return None, "請上傳圖片", gr.update(visible=False)
244
 
245
- result_path, status = process_image(image)
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
- process_btn.click(
252
- fn=process_and_download,
253
- inputs=[input_image],
254
  outputs=[output_image, status_text, download_btn]
255
  )
256
 
257
- # 裁切圖片處理
258
- crop_image.select(
259
- fn=manual_crop_image,
260
- inputs=[crop_image, crop_image.select],
261
- outputs=[output_image, status_text]
 
 
 
 
 
 
 
 
 
 
262
  )
263
 
264
- # 當圖片上傳時自動更新狀態
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  input_image.change(
266
- fn=lambda img: "圖片已上傳,可以開始處理!" if img is not None else "請上傳圖片",
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
  # 啟動應用程式