PSNbst commited on
Commit
4bbef93
·
verified ·
1 Parent(s): 14c46f5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +55 -84
app.py CHANGED
@@ -3,48 +3,40 @@ from PIL import Image
3
  import io
4
  import zipfile
5
 
6
- def resize_image_to_multiple_of_64(img: Image.Image, fill_color=(0, 0, 0)):
7
- """
8
- 将单张图片缩放到 (w', h'),其中 w'、h' 均为 64 的整数倍。
9
- 若原图宽高比与 w'/h' 不完全一致,则在空余处用 fill_color 填充(不裁切)。
10
- """
11
  w, h = img.size
12
-
13
- # 计算最接近的 64 倍数
14
- # 若 round(...) 结果为 0,至少保底 64
15
  w64 = max(64, round(w / 64) * 64)
16
  h64 = max(64, round(h / 64) * 64)
17
 
18
- # 根据最小缩放策略,保证图片能放进 w64 × h64
19
  scale = min(w64 / w, h64 / h)
20
  new_width = int(w * scale)
21
  new_height = int(h * scale)
22
 
23
- # 创建背景
24
- new_img = Image.new("RGB", (w64, h64), fill_color)
25
- # 将原图缩放
26
  scaled_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
27
- # 居中贴到背景上
 
28
  offset_x = (w64 - new_width) // 2
29
  offset_y = (h64 - new_height) // 2
30
- new_img.paste(scaled_img, (offset_x, offset_y))
31
- return new_img
32
 
33
- def make_collage(four_images, fill_color=(0, 0, 0)):
34
- """
35
- 4 张同样尺寸的图片(2×2)拼接成一张大图。
36
- 如果结果大于 2048×2048,则等比例缩小到不超过 2048。
37
- """
38
- assert len(four_images) == 4, "必须是 4 张图"
39
- w, h = four_images[0].size # 假设 4 张图尺寸相同
40
- collage = Image.new("RGB", (2 * w, 2 * h), fill_color)
41
- # 依次贴上
42
- collage.paste(four_images[0], (0, 0))
43
- collage.paste(four_images[1], (w, 0))
44
- collage.paste(four_images[2], (0, h))
45
- collage.paste(four_images[3], (w, h))
46
 
47
  cw, ch = collage.size
 
48
  if cw > 2048 or ch > 2048:
49
  scale = min(2048 / cw, 2048 / ch)
50
  new_cw = int(cw * scale)
@@ -53,99 +45,78 @@ def make_collage(four_images, fill_color=(0, 0, 0)):
53
 
54
  return collage
55
 
56
- def process_images(uploaded_files):
57
- """
58
- 处理用户上传的多张图片:
59
- 1. 每组 4 张图 -> 统一到 64 的倍数尺寸 -> 拼成 2×2 的大图
60
- 2. 大于 2048×2048 则缩小
61
- 3. 返回多张拼接图(以列表的形式)供 Gradio 显示
62
- 4. 若最后一组不足 4 张,则跳过
63
- """
64
- # 读取成 PIL 对象
65
  pil_images = []
66
- for file_obj in uploaded_files:
67
- if file_obj is not None:
68
- img = Image.open(file_obj.name).convert("RGB")
 
69
  pil_images.append(img)
70
-
71
  results = []
72
- # 4 张为一组进行处理
73
  for i in range(0, len(pil_images), 4):
74
  group = pil_images[i : i + 4]
75
  if len(group) < 4:
76
- # 不足 4 张,跳过
77
  break
78
-
79
- # 分别 resize
80
- resized = [resize_image_to_multiple_of_64(img, fill_color=(0,0,0)) for img in group]
81
- # 拼接
82
- collage = make_collage(resized, fill_color=(0,0,0))
83
  results.append(collage)
84
 
85
  return results
86
 
87
- def process_and_zip(uploaded_files):
88
- """
89
- 在内存中创建 Zip,将所有生成的拼接图打包。
90
- 返回一个 BytesIO 给 Gradio 的 File 输出,用于下载。
91
- """
92
- # 先按需求生成拼接结果
93
- collages = process_images(uploaded_files)
94
-
95
- # 如果没有生成任何拼接图,则返回 None,让前端不显示下载
96
  if not collages:
 
97
  return None
98
 
99
- # 在内存中创建 zip
100
  buf = io.BytesIO()
101
- with zipfile.ZipFile(buf, "w") as zf:
102
  for idx, img in enumerate(collages):
103
- # PIL 图像转成字节并写入 zip
104
  img_bytes = io.BytesIO()
 
105
  img.save(img_bytes, format="PNG")
106
  img_bytes.seek(0)
107
  zf.writestr(f"collage_{idx+1}.png", img_bytes.read())
108
  buf.seek(0)
109
  return buf
110
 
111
- ########################################
112
- # 构建 Gradio 界面
113
- ########################################
114
- css_custom = """
115
- /* 你可根据需要,在此自定义或覆盖 Gradio 的 CSS 样式 */
116
- """
117
-
118
- with gr.Blocks(css=css_custom) as demo:
119
  gr.Markdown("## 图片 2×2 拼接小工具")
120
  gr.Markdown(
121
- "1. 请选择若干张图片(数量需为 4 的倍数才能完整拼接)。<br>"
122
- "2. 4 张会被缩放并以 2×2 的形式合并成新图。<br>"
123
- "3. 如果合并后图像尺寸超过 2048×2048,会自动缩小。<br>"
124
- "4. 你可以在下方预览结果或打包下载。"
 
125
  )
126
 
127
  with gr.Row():
128
  with gr.Column():
129
  file_input = gr.Files(label="上传图片(可多选)", file_types=["image"])
130
- btn_preview = gr.Button("生成预览")
131
- btn_zip = gr.Button("打包下载 ZIP")
132
-
133
  with gr.Column():
134
- gallery_output = gr.Gallery(label="拼接结果预览").style(grid=[2], height="auto")
135
- zip_file = gr.File(label="下载拼接结果 ZIP", interactive=False, visible=False)
 
136
 
137
- # 点击“生成预览” -> 显示拼接好的图片(可能多张)
138
- btn_preview.click(
139
- fn=process_images,
140
  inputs=[file_input],
141
  outputs=[gallery_output]
142
  )
143
 
144
- # 点击“打包下载 ZIP” -> 生成 zip 文件供下载
145
- btn_zip.click(
146
- fn=process_and_zip,
147
  inputs=[file_input],
148
- outputs=[zip_file]
149
  )
150
 
151
  demo.launch()
 
3
  import io
4
  import zipfile
5
 
6
+ def resize_image_to_multiple_of_64_rgba(img: Image.Image):
 
 
 
 
7
  w, h = img.size
8
+ # 计算最接近的 64 倍数(保底64)
 
 
9
  w64 = max(64, round(w / 64) * 64)
10
  h64 = max(64, round(h / 64) * 64)
11
 
12
+ # 按最小缩放比,使原图能贴进 w64 x h64
13
  scale = min(w64 / w, h64 / h)
14
  new_width = int(w * scale)
15
  new_height = int(h * scale)
16
 
17
+ # 用 RGBA、黑色背景(全不透明)
18
+ background = Image.new("RGBA", (w64, h64), (0, 0, 0, 255))
19
+ # 缩放原图到 new_width, new_height
20
  scaled_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
21
+
22
+ # 贴到背景中央,保留其透明度
23
  offset_x = (w64 - new_width) // 2
24
  offset_y = (h64 - new_height) // 2
25
+ background.paste(scaled_img, (offset_x, offset_y), scaled_img)
26
+ return background
27
 
28
+ def make_collage_2x2(four_rgba_images):
29
+ # 4 张图,均已是 RGBA、同尺寸
30
+ w, h = four_rgba_images[0].size
31
+ # 新画布:2x2 => 宽度 2*w,高度 2*h,背景黑
32
+ collage = Image.new("RGBA", (2 * w, 2 * h), (0, 0, 0, 255))
33
+ collage.paste(four_rgba_images[0], (0, 0), four_rgba_images[0])
34
+ collage.paste(four_rgba_images[1], (w, 0), four_rgba_images[1])
35
+ collage.paste(four_rgba_images[2], (0, h), four_rgba_images[2])
36
+ collage.paste(four_rgba_images[3], (w, h), four_rgba_images[3])
 
 
 
 
37
 
38
  cw, ch = collage.size
39
+ # 若合并后任意边 > 2048,则等比例缩小
40
  if cw > 2048 or ch > 2048:
41
  scale = min(2048 / cw, 2048 / ch)
42
  new_cw = int(cw * scale)
 
45
 
46
  return collage
47
 
48
+ def process_images_for_preview(uploaded_files):
 
 
 
 
 
 
 
 
49
  pil_images = []
50
+ for f in uploaded_files:
51
+ if f is not None:
52
+ # RGBA 读图,保证可保留透明通道
53
+ img = Image.open(f.name).convert("RGBA")
54
  pil_images.append(img)
55
+
56
  results = []
57
+ # 4 张为一组,不足 4 张跳过
58
  for i in range(0, len(pil_images), 4):
59
  group = pil_images[i : i + 4]
60
  if len(group) < 4:
 
61
  break
62
+ # 每张做64倍数 resize + 黑色背景填充
63
+ resized = [resize_image_to_multiple_of_64_rgba(im) for im in group]
64
+ # 2×2 拼接
65
+ collage = make_collage_2x2(resized)
 
66
  results.append(collage)
67
 
68
  return results
69
 
70
+ def process_and_zip_for_download(uploaded_files):
71
+ collages = process_images_for_preview(uploaded_files)
 
 
 
 
 
 
 
72
  if not collages:
73
+ # 没有任何拼接图,就返回 None,让界面不显示可下载链接
74
  return None
75
 
 
76
  buf = io.BytesIO()
77
+ with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
78
  for idx, img in enumerate(collages):
79
+ # 转字节并写入 zip
80
  img_bytes = io.BytesIO()
81
+ # 以 PNG 格式保存,带 RGBA
82
  img.save(img_bytes, format="PNG")
83
  img_bytes.seek(0)
84
  zf.writestr(f"collage_{idx+1}.png", img_bytes.read())
85
  buf.seek(0)
86
  return buf
87
 
88
+ with gr.Blocks() as demo:
 
 
 
 
 
 
 
89
  gr.Markdown("## 图片 2×2 拼接小工具")
90
  gr.Markdown(
91
+ "1. 一次可上传多张图片,每 4 张为一组。\n"
92
+ "2. 每组里 4 张图会先缩放到 64 的倍数、空余处用黑色填充,然后 2×2 拼接。\n"
93
+ "3. 拼接结果若超过 2048×2048,则缩小到不超过 2048。\n"
94
+ "4. 不足 4 张的剩余图片忽略。\n"
95
+ "5. 支持保留 PNG 透明度,拼图时使用黑色背景。"
96
  )
97
 
98
  with gr.Row():
99
  with gr.Column():
100
  file_input = gr.Files(label="上传图片(可多选)", file_types=["image"])
101
+ preview_btn = gr.Button("生成预览")
102
+ download_btn = gr.Button("打包下载 ZIP")
 
103
  with gr.Column():
104
+ # Gallery 构造上不使用 .style() 以兼容老版本
105
+ gallery_output = gr.Gallery(label="拼接结果预览", columns=2)
106
+ zip_output = gr.File(label="下载拼接结果 ZIP", interactive=False, visible=False)
107
 
108
+ # 点击“生成预览” -> 返回拼接图列表
109
+ preview_btn.click(
110
+ fn=process_images_for_preview,
111
  inputs=[file_input],
112
  outputs=[gallery_output]
113
  )
114
 
115
+ # 点击“打包下载 ZIP” -> 生成 zip
116
+ download_btn.click(
117
+ fn=process_and_zip_for_download,
118
  inputs=[file_input],
119
+ outputs=[zip_output]
120
  )
121
 
122
  demo.launch()