dseditor commited on
Commit
ecfbfd1
·
verified ·
1 Parent(s): d935ae6

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +19 -82
app.py CHANGED
@@ -11,30 +11,22 @@ def resize_images_to_match(images, resize_mode="height"):
11
  if not images or len(images) == 0:
12
  return []
13
 
14
- # 過濾掉None值
15
  valid_images = [img for img in images if img is not None]
16
  if not valid_images:
17
  return []
18
 
19
  if resize_mode == "height":
20
- # 找到最小高度,保持寬高比調整
21
  target_height = min(img.size[1] for img in valid_images)
22
  resized_images = []
23
-
24
  for img in valid_images:
25
- # 計算新寬度以保持寬高比
26
  aspect_ratio = img.size[0] / img.size[1]
27
  new_width = int(target_height * aspect_ratio)
28
  resized_img = img.resize((new_width, target_height), Image.Resampling.LANCZOS)
29
  resized_images.append(resized_img)
30
-
31
  else: # width mode
32
- # 找到最小寬度,保持寬高比調整
33
  target_width = min(img.size[0] for img in valid_images)
34
  resized_images = []
35
-
36
  for img in valid_images:
37
- # 計算新高度以保持寬高比
38
  aspect_ratio = img.size[1] / img.size[0]
39
  new_height = int(target_width * aspect_ratio)
40
  resized_img = img.resize((target_width, new_height), Image.Resampling.LANCZOS)
@@ -52,16 +44,13 @@ def concatenate_images(images, direction="horizontal", spacing=0, background_col
52
  if not images or len(images) == 0:
53
  return None
54
 
55
- # 過濾掉None值
56
  valid_images = [img for img in images if img is not None]
57
  if not valid_images:
58
  return None
59
 
60
- # 確保所有圖片都轉換為RGB模式(去除透明通道)
61
  rgb_images = []
62
  for img in valid_images:
63
  if img.mode != 'RGB':
64
- # 如果有透明通道,先貼到白色背景上
65
  if img.mode in ('RGBA', 'LA'):
66
  background = Image.new('RGB', img.size, 'white')
67
  background.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None)
@@ -75,31 +64,20 @@ def concatenate_images(images, direction="horizontal", spacing=0, background_col
75
  return rgb_images[0]
76
 
77
  if direction == "horizontal":
78
- # 水平拼接
79
  total_width = sum(img.size[0] for img in rgb_images) + spacing * (len(rgb_images) - 1)
80
  max_height = max(img.size[1] for img in rgb_images)
81
-
82
- # 創建新圖片 - 確保為RGB模式
83
  result = Image.new('RGB', (total_width, max_height), background_color)
84
-
85
  x_offset = 0
86
  for img in rgb_images:
87
- # 垂直居中放置
88
  y_offset = (max_height - img.size[1]) // 2
89
  result.paste(img, (x_offset, y_offset))
90
  x_offset += img.size[0] + spacing
91
-
92
- else: # vertical
93
- # 垂直拼接
94
  max_width = max(img.size[0] for img in rgb_images)
95
  total_height = sum(img.size[1] for img in rgb_images) + spacing * (len(rgb_images) - 1)
96
-
97
- # 創建新圖片 - 確保為RGB模式
98
  result = Image.new('RGB', (max_width, total_height), background_color)
99
-
100
  y_offset = 0
101
  for img in rgb_images:
102
- # 水平居中放置
103
  x_offset = (max_width - img.size[0]) // 2
104
  result.paste(img, (x_offset, y_offset))
105
  y_offset += img.size[1] + spacing
@@ -113,33 +91,27 @@ def save_as_jpg(image, quality=95):
113
  if image is None:
114
  return None
115
 
116
- # 確保圖片為RGB模式
117
  if image.mode != 'RGB':
118
  if image.mode in ('RGBA', 'LA'):
119
- # 有透明通道的圖片,貼到白色背景上
120
  background = Image.new('RGB', image.size, 'white')
121
- if image.mode == 'RGBA':
122
- background.paste(image, mask=image.split()[-1])
123
- else:
124
- background.paste(image, mask=image.split()[-1])
125
  image = background
126
  else:
127
  image = image.convert('RGB')
128
 
129
- # 保存到bytes buffer
130
  buffer = io.BytesIO()
131
  image.save(buffer, format='JPEG', quality=quality, optimize=True)
132
  buffer.seek(0)
133
-
134
- # 重新加載為PIL圖片
135
  return Image.open(buffer)
 
 
 
 
 
136
  """
137
  處理圖片拼接的主函數
138
  """
139
- # 收集所有上傳的圖片
140
  uploaded_images = [image1, image2, image3, image4, image5, image6]
141
-
142
- # 過濾掉None值
143
  valid_images = [img for img in uploaded_images if img is not None]
144
 
145
  if len(valid_images) == 0:
@@ -149,37 +121,34 @@ def save_as_jpg(image, quality=95):
149
  return valid_images[0], f"✅ 只有一張圖片,無需拼接"
150
 
151
  try:
152
- # 調整圖片尺寸
153
  if resize_mode == "等高度 (水平拼接)":
154
  resized_images = resize_images_to_match(valid_images, "height")
155
  direction = "horizontal"
156
- else: # 等寬度 (垂直拼��)
157
  resized_images = resize_images_to_match(valid_images, "width")
158
  direction = "vertical"
159
 
160
- # 如果用戶選擇了不同的拼接方向,使用用戶選擇
161
  if concat_direction != "自動 (根據調整模式)":
162
  direction = "horizontal" if concat_direction == "水平拼接" else "vertical"
163
 
164
- # 拼接圖片
165
  result = concatenate_images(resized_images, direction, spacing, bg_color)
166
-
167
  if result is None:
168
- return None, "❌ 拼接失败"
 
 
169
 
170
- # 返回結果和統計信息
171
  info = f"✅ 成功拼接 {len(valid_images)} 張圖片\n"
172
  info += f"📐 最終尺寸: {result.size[0]} x {result.size[1]} 像素\n"
173
  info += f"🔄 調整模式: {resize_mode}\n"
174
  info += f"➡️ 拼接方向: {'水平' if direction == 'horizontal' else '垂直'}\n"
175
- info += f"📏 間距: {spacing} 像素"
 
176
 
177
  return result, info
178
 
179
  except Exception as e:
180
  return None, f"❌ 處理過程中出現錯誤: {str(e)}"
181
 
182
- # 創建Gradio界面
183
  def create_interface():
184
  with gr.Blocks(title="圖片拼接比較工具", theme=gr.themes.Soft()) as iface:
185
  gr.Markdown("""
@@ -197,71 +166,45 @@ def create_interface():
197
  with gr.Row():
198
  with gr.Column(scale=2):
199
  gr.Markdown("### 📁 上傳圖片")
200
-
201
- # 圖片上傳組件
202
  with gr.Row():
203
  image1 = gr.Image(type="pil", label="圖片 1")
204
  image2 = gr.Image(type="pil", label="圖片 2")
205
  image3 = gr.Image(type="pil", label="圖片 3")
206
-
207
  with gr.Row():
208
  image4 = gr.Image(type="pil", label="圖片 4")
209
  image5 = gr.Image(type="pil", label="圖片 5")
210
  image6 = gr.Image(type="pil", label="圖片 6")
211
 
212
  gr.Markdown("### ⚙️ 處理設定")
213
-
214
  with gr.Row():
215
  resize_mode = gr.Dropdown(
216
  choices=["等高度 (水平拼接)", "等寬度 (垂直拼接)"],
217
  value="等高度 (水平拼接)",
218
  label="尺寸調整模式"
219
  )
220
-
221
  concat_direction = gr.Dropdown(
222
  choices=["自動 (根據調整模式)", "水平拼接", "垂直拼接"],
223
  value="自動 (根據調整模式)",
224
  label="拼接方向"
225
  )
226
-
227
  with gr.Row():
228
- spacing = gr.Slider(
229
- minimum=0,
230
- maximum=50,
231
- value=2,
232
- step=1,
233
- label="圖片間距 (像素)"
234
- )
235
-
236
  bg_color = gr.Dropdown(
237
  choices=["white", "black", "gray"],
238
  value="white",
239
  label="背景顏色"
240
  )
241
-
242
- # JPG品質設定
243
- jpg_quality = gr.Slider(
244
- minimum=60,
245
- maximum=100,
246
- value=95,
247
- step=5,
248
- label="JPG 輸出品質 (%)"
249
- )
250
-
251
  process_btn = gr.Button("🚀 開始處理", variant="primary", size="lg")
252
-
253
  with gr.Column(scale=2):
254
  gr.Markdown("### 🎯 處理結果")
255
  result_image = gr.Image(type="pil", label="拼接結果")
256
  result_info = gr.Textbox(label="處理信息", lines=6, max_lines=10)
257
 
258
- # 綁定處理函數
259
  process_btn.click(
260
  fn=process_images,
261
- inputs=[
262
- image1, image2, image3, image4, image5, image6,
263
- resize_mode, concat_direction, spacing, bg_color, jpg_quality
264
- ],
265
  outputs=[result_image, result_info]
266
  )
267
 
@@ -271,18 +214,12 @@ def create_interface():
271
  - **等寬度模式**:所有圖片調整為相同寬度,適合垂直對比
272
  - 支持常見圖片格式:PNG, JPG, JPEG, WEBP
273
  - 處理過程保持圖片原始寬高比
274
- - **輸出格式:JPG** - ���美兼容 Medium 等平台
275
  - **品質建議**:95% 提供最佳品質,85% 平衡品質與檔案大小
276
  - 自動處理透明背景(轉為白色背景)
277
  """)
278
-
279
  return iface
280
 
281
- # 啟動應用
282
  if __name__ == "__main__":
283
  app = create_interface()
284
- app.launch(
285
- server_name="0.0.0.0",
286
- server_port=7860,
287
- share=True
288
- )
 
11
  if not images or len(images) == 0:
12
  return []
13
 
 
14
  valid_images = [img for img in images if img is not None]
15
  if not valid_images:
16
  return []
17
 
18
  if resize_mode == "height":
 
19
  target_height = min(img.size[1] for img in valid_images)
20
  resized_images = []
 
21
  for img in valid_images:
 
22
  aspect_ratio = img.size[0] / img.size[1]
23
  new_width = int(target_height * aspect_ratio)
24
  resized_img = img.resize((new_width, target_height), Image.Resampling.LANCZOS)
25
  resized_images.append(resized_img)
 
26
  else: # width mode
 
27
  target_width = min(img.size[0] for img in valid_images)
28
  resized_images = []
 
29
  for img in valid_images:
 
30
  aspect_ratio = img.size[1] / img.size[0]
31
  new_height = int(target_width * aspect_ratio)
32
  resized_img = img.resize((target_width, new_height), Image.Resampling.LANCZOS)
 
44
  if not images or len(images) == 0:
45
  return None
46
 
 
47
  valid_images = [img for img in images if img is not None]
48
  if not valid_images:
49
  return None
50
 
 
51
  rgb_images = []
52
  for img in valid_images:
53
  if img.mode != 'RGB':
 
54
  if img.mode in ('RGBA', 'LA'):
55
  background = Image.new('RGB', img.size, 'white')
56
  background.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None)
 
64
  return rgb_images[0]
65
 
66
  if direction == "horizontal":
 
67
  total_width = sum(img.size[0] for img in rgb_images) + spacing * (len(rgb_images) - 1)
68
  max_height = max(img.size[1] for img in rgb_images)
 
 
69
  result = Image.new('RGB', (total_width, max_height), background_color)
 
70
  x_offset = 0
71
  for img in rgb_images:
 
72
  y_offset = (max_height - img.size[1]) // 2
73
  result.paste(img, (x_offset, y_offset))
74
  x_offset += img.size[0] + spacing
75
+ else:
 
 
76
  max_width = max(img.size[0] for img in rgb_images)
77
  total_height = sum(img.size[1] for img in rgb_images) + spacing * (len(rgb_images) - 1)
 
 
78
  result = Image.new('RGB', (max_width, total_height), background_color)
 
79
  y_offset = 0
80
  for img in rgb_images:
 
81
  x_offset = (max_width - img.size[0]) // 2
82
  result.paste(img, (x_offset, y_offset))
83
  y_offset += img.size[1] + spacing
 
91
  if image is None:
92
  return None
93
 
 
94
  if image.mode != 'RGB':
95
  if image.mode in ('RGBA', 'LA'):
 
96
  background = Image.new('RGB', image.size, 'white')
97
+ background.paste(image, mask=image.split()[-1])
 
 
 
98
  image = background
99
  else:
100
  image = image.convert('RGB')
101
 
 
102
  buffer = io.BytesIO()
103
  image.save(buffer, format='JPEG', quality=quality, optimize=True)
104
  buffer.seek(0)
 
 
105
  return Image.open(buffer)
106
+
107
+ def process_images(
108
+ image1, image2, image3, image4, image5, image6,
109
+ resize_mode, concat_direction, spacing, bg_color, jpg_quality
110
+ ):
111
  """
112
  處理圖片拼接的主函數
113
  """
 
114
  uploaded_images = [image1, image2, image3, image4, image5, image6]
 
 
115
  valid_images = [img for img in uploaded_images if img is not None]
116
 
117
  if len(valid_images) == 0:
 
121
  return valid_images[0], f"✅ 只有一張圖片,無需拼接"
122
 
123
  try:
 
124
  if resize_mode == "等高度 (水平拼接)":
125
  resized_images = resize_images_to_match(valid_images, "height")
126
  direction = "horizontal"
127
+ else:
128
  resized_images = resize_images_to_match(valid_images, "width")
129
  direction = "vertical"
130
 
 
131
  if concat_direction != "自動 (根據調整模式)":
132
  direction = "horizontal" if concat_direction == "水平拼接" else "vertical"
133
 
 
134
  result = concatenate_images(resized_images, direction, spacing, bg_color)
 
135
  if result is None:
136
+ return None, "❌ 拼接失敗"
137
+
138
+ result = save_as_jpg(result, quality=jpg_quality)
139
 
 
140
  info = f"✅ 成功拼接 {len(valid_images)} 張圖片\n"
141
  info += f"📐 最終尺寸: {result.size[0]} x {result.size[1]} 像素\n"
142
  info += f"🔄 調整模式: {resize_mode}\n"
143
  info += f"➡️ 拼接方向: {'水平' if direction == 'horizontal' else '垂直'}\n"
144
+ info += f"📏 間距: {spacing} 像素\n"
145
+ info += f"💾 輸出品質: {jpg_quality}%"
146
 
147
  return result, info
148
 
149
  except Exception as e:
150
  return None, f"❌ 處理過程中出現錯誤: {str(e)}"
151
 
 
152
  def create_interface():
153
  with gr.Blocks(title="圖片拼接比較工具", theme=gr.themes.Soft()) as iface:
154
  gr.Markdown("""
 
166
  with gr.Row():
167
  with gr.Column(scale=2):
168
  gr.Markdown("### 📁 上傳圖片")
 
 
169
  with gr.Row():
170
  image1 = gr.Image(type="pil", label="圖片 1")
171
  image2 = gr.Image(type="pil", label="圖片 2")
172
  image3 = gr.Image(type="pil", label="圖片 3")
 
173
  with gr.Row():
174
  image4 = gr.Image(type="pil", label="圖片 4")
175
  image5 = gr.Image(type="pil", label="圖片 5")
176
  image6 = gr.Image(type="pil", label="圖片 6")
177
 
178
  gr.Markdown("### ⚙️ 處理設定")
 
179
  with gr.Row():
180
  resize_mode = gr.Dropdown(
181
  choices=["等高度 (水平拼接)", "等寬度 (垂直拼接)"],
182
  value="等高度 (水平拼接)",
183
  label="尺寸調整模式"
184
  )
 
185
  concat_direction = gr.Dropdown(
186
  choices=["自動 (根據調整模式)", "水平拼接", "垂直拼接"],
187
  value="自動 (根據調整模式)",
188
  label="拼接方向"
189
  )
 
190
  with gr.Row():
191
+ spacing = gr.Slider(0, 50, value=2, step=1, label="圖片間距 (像素)")
 
 
 
 
 
 
 
192
  bg_color = gr.Dropdown(
193
  choices=["white", "black", "gray"],
194
  value="white",
195
  label="背景顏色"
196
  )
197
+ jpg_quality = gr.Slider(60, 100, value=95, step=5, label="JPG 輸出品質 (%)")
 
 
 
 
 
 
 
 
 
198
  process_btn = gr.Button("🚀 開始處理", variant="primary", size="lg")
 
199
  with gr.Column(scale=2):
200
  gr.Markdown("### 🎯 處理結果")
201
  result_image = gr.Image(type="pil", label="拼接結果")
202
  result_info = gr.Textbox(label="處理信息", lines=6, max_lines=10)
203
 
 
204
  process_btn.click(
205
  fn=process_images,
206
+ inputs=[image1, image2, image3, image4, image5, image6,
207
+ resize_mode, concat_direction, spacing, bg_color, jpg_quality],
 
 
208
  outputs=[result_image, result_info]
209
  )
210
 
 
214
  - **等寬度模式**:所有圖片調整為相同寬度,適合垂直對比
215
  - 支持常見圖片格式:PNG, JPG, JPEG, WEBP
216
  - 處理過程保持圖片原始寬高比
217
+ - **輸出格式:JPG**
218
  - **品質建議**:95% 提供最佳品質,85% 平衡品質與檔案大小
219
  - 自動處理透明背景(轉為白色背景)
220
  """)
 
221
  return iface
222
 
 
223
  if __name__ == "__main__":
224
  app = create_interface()
225
+ app.launch(server_name="0.0.0.0", server_port=7860, share=True)