Spaces:
Runtime error
Runtime error
File size: 8,281 Bytes
ad22797 14c46f5 af3dd3e 1898bb9 af3dd3e 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14c46f5 1898bb9 14df9f4 1898bb9 14df9f4 4bbef93 af3dd3e 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 af3dd3e 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14c46f5 4bbef93 14df9f4 4bbef93 14c46f5 1898bb9 14c46f5 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14df9f4 1898bb9 14c46f5 af3dd3e 1898bb9 14df9f4 1898bb9 14df9f4 14c46f5 14df9f4 14c46f5 af3dd3e 4bbef93 1898bb9 14c46f5 1898bb9 af3dd3e 14c46f5 af3dd3e 14df9f4 4bbef93 14df9f4 af3dd3e 14c46f5 14df9f4 14c46f5 14df9f4 14c46f5 14df9f4 af3dd3e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
import gradio as gr
from PIL import Image
import io
import zipfile
import random
def random_black_or_white():
"""返回 RGBA (黑 或 白),50% 概率"""
return (0, 0, 0, 255) if random.random() < 0.5 else (255, 255, 255, 255)
def random_non_black_white():
"""
返回 (r,g,b,a),且不是纯黑(0,0,0)也不是纯白(255,255,255)。
用于最后不足4张时的空格颜色填充。
"""
while True:
r = random.randint(0, 255)
g = random.randint(0, 255)
b = random.randint(0, 255)
if not (r == g == b == 0 or r == g == b == 255):
return (r, g, b, 255)
def limit_2048(img: Image.Image):
"""若宽或高大于2048,则等比例缩小到不超过2048。"""
w, h = img.size
if w > 2048 or h > 2048:
scale = min(2048 / w, 2048 / h)
new_w = int(w * scale)
new_h = int(h * scale)
img = img.resize((new_w, new_h), Image.Resampling.LANCZOS)
return img
def resize_to_64_multiple(img: Image.Image):
"""
将图像就近缩放到(w', h'),其中 w'、h'均为64倍数(至少64)。
空余区域随机黑/白填充,保留透明度。
"""
w, h = img.size
w64 = max(64, round(w / 64) * 64)
h64 = max(64, round(h / 64) * 64)
scale = min(w64 / w, h64 / h)
nw = int(w * scale)
nh = int(h * scale)
bg_color = random_black_or_white()
background = Image.new("RGBA", (w64, h64), bg_color)
scaled = img.resize((nw, nh), Image.Resampling.LANCZOS)
ox = (w64 - nw) // 2
oy = (h64 - nh) // 2
background.paste(scaled, (ox, oy), scaled)
return background
def make_collage_2x2(images_4):
"""传入4张同尺寸RGBA,以2×2拼接,并限制在2048以内。"""
w, h = images_4[0].size
collage = Image.new("RGBA", (2*w, 2*h), (0, 0, 0, 255))
collage.paste(images_4[0], (0, 0), images_4[0])
collage.paste(images_4[1], (w, 0), images_4[1])
collage.paste(images_4[2], (0, h), images_4[2])
collage.paste(images_4[3], (w, h), images_4[3])
return limit_2048(collage)
def make_collage_leftover(images_leftover):
"""
针对不足4张(1~3)的情况,随机布局(1x1,1x2,2x1,2x2等)容纳这些图片,
其余空格以“非黑非白”的纯色填充,并最终限制2048以内。
"""
n = len(images_leftover)
if n < 1 or n > 3:
return None
# 先做“64倍数随机黑/白填充”缩放
resized_list = [resize_to_64_multiple(img) for img in images_leftover]
# 找最大宽、高,统一尺寸
max_w = max(im.size[0] for im in resized_list)
max_h = max(im.size[1] for im in resized_list)
# 再居中贴到背景 (max_w, max_h) 保证每张图一致
uniformed = []
for rimg in resized_list:
w, h = rimg.size
if (w, h) == (max_w, max_h):
uniformed.append(rimg)
else:
# 用本身的背景色(左上像素)来填充
bg_color = rimg.getpixel((0,0))
bg = Image.new("RGBA", (max_w, max_h), bg_color)
offx = (max_w - w) // 2
offy = (max_h - h) // 2
bg.paste(rimg, (offx, offy), rimg)
uniformed.append(bg)
# 选定随机布局
# n=1: (1x1),(1x2),(2x1),(2x2)
# n=2: (1x2),(2x1),(2x2)
# n=3: (2x2)
possible_layouts = []
if n == 1:
possible_layouts = [(1,1), (1,2), (2,1), (2,2)]
elif n == 2:
possible_layouts = [(1,2), (2,1), (2,2)]
else: # n == 3
possible_layouts = [(2,2)]
rows, cols = random.choice(possible_layouts)
big_w = cols * max_w
big_h = rows * max_h
collage = Image.new("RGBA", (big_w, big_h), (0,0,0,255))
# 网格坐标
cells = [(r, c) for r in range(rows) for c in range(cols)]
random.shuffle(cells)
# 把uniformed中的图贴到前n个格子
for i, img_ in enumerate(uniformed):
r, c = cells[i]
offset_x = c * max_w
offset_y = r * max_h
collage.paste(img_, (offset_x, offset_y), img_)
# 剩余格子(空格)用“非黑非白”的随机色填充
leftover_cells = cells[n:]
for (r, c) in leftover_cells:
color_ = random_non_black_white()
rect = Image.new("RGBA", (max_w, max_h), color_)
collage.paste(rect, (c*max_w, r*max_h), rect)
return limit_2048(collage)
def process_images(uploaded_files):
"""
1) 分成若干4张组 => each 2x2;
2) 对最后剩余(1~3张),make_collage_leftover 处理。
3) 返回所有结果图
"""
pil_images = []
for f in uploaded_files:
if f is not None:
# 保留透明
img = Image.open(f.name).convert("RGBA")
pil_images.append(img)
results = []
total = len(pil_images)
groups_4 = total // 4
leftover = total % 4
idx = 0
# 先拼满4张的组
for _ in range(groups_4):
group_4 = pil_images[idx:idx+4]
idx += 4
# 每张先resize
resized_4 = [resize_to_64_multiple(im) for im in group_4]
# 再统一max_w, max_h
max_w = max(im.size[0] for im in resized_4)
max_h = max(im.size[1] for im in resized_4)
final_4 = []
for rimg in resized_4:
w, h = rimg.size
if (w, h) == (max_w, max_h):
final_4.append(rimg)
else:
# 补背景居中
bg_color = rimg.getpixel((0,0))
bg = Image.new("RGBA", (max_w, max_h), bg_color)
offx = (max_w - w)//2
offy = (max_h - h)//2
bg.paste(rimg, (offx, offy), rimg)
final_4.append(bg)
collage_2x2 = make_collage_2x2(final_4)
results.append(collage_2x2)
# 再拼 leftover 1~3张
if leftover > 0:
leftover_images = pil_images[idx:]
collage_left = make_collage_leftover(leftover_images)
if collage_left is not None:
results.append(collage_left)
return results
def make_zip(uploaded_files):
"""把所有拼接结果打包成zip并返回给Gradio的File组件。"""
collages = process_images(uploaded_files)
# 若无生成任何拼图
if not collages:
# 返回 None 说明无法下载;会显示“无可下载内容”提示
return None
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
for i, img in enumerate(collages, start=1):
img_bytes = io.BytesIO()
img.save(img_bytes, format="PNG")
img_bytes.seek(0)
zf.writestr(f"collage_{i}.png", img_bytes.read())
buf.seek(0)
return buf
def on_zip_click(files):
"""
用来返回 (zip_file_obj, message_str) 两个输出:
1) zip_file_obj 要么是zip流,要么是None;
2) message_str 用于提示结果或错误。
"""
z = make_zip(files)
if z is None:
return (None, "无可下载内容 - 请检查是否上传了图片或剩余不足4张且无法拼图")
else:
return (z, "打包完成!可点击上方链接下载。")
with gr.Blocks() as demo:
gr.Markdown("## 2×2 拼接小工具(支持最后不足4张、随机填充、保留透明)")
with gr.Row():
with gr.Column():
file_input = gr.Files(label="上传多张图片", file_types=["image"])
preview_btn = gr.Button("生成预览")
zip_btn = gr.Button("打包下载 ZIP")
with gr.Column():
# 不使用 .style() 以兼容老Gradio
gallery_out = gr.Gallery(label="拼接结果预览", columns=2)
# 一开始就 visible=True,这样点击按钮后能马上显示下载链接
zip_file_out = gr.File(label="点击下载打包结果", visible=True, interactive=False)
msg_output = gr.Textbox(label="处理信息", interactive=False)
# 生成预览
preview_btn.click(
fn=process_images,
inputs=[file_input],
outputs=[gallery_out]
)
# 打包下载ZIP,额外给一个文本提示
zip_btn.click(
fn=on_zip_click,
inputs=[file_input],
outputs=[zip_file_out, msg_output]
)
demo.launch() |