File size: 4,510 Bytes
ad22797
14c46f5
af3dd3e
 
 
4bbef93
14c46f5
4bbef93
14c46f5
 
 
4bbef93
14c46f5
 
 
 
4bbef93
 
 
14c46f5
4bbef93
 
14c46f5
 
4bbef93
 
af3dd3e
4bbef93
 
 
 
 
 
 
 
 
14c46f5
 
4bbef93
14c46f5
 
 
 
 
 
 
af3dd3e
4bbef93
14c46f5
4bbef93
 
 
 
14c46f5
4bbef93
14c46f5
4bbef93
14c46f5
 
 
 
4bbef93
 
 
 
14c46f5
 
 
af3dd3e
4bbef93
 
14c46f5
4bbef93
14c46f5
 
af3dd3e
4bbef93
14c46f5
4bbef93
14c46f5
4bbef93
14c46f5
 
 
af3dd3e
14c46f5
af3dd3e
4bbef93
14c46f5
 
4bbef93
 
 
 
 
14c46f5
 
af3dd3e
14c46f5
 
4bbef93
 
14c46f5
4bbef93
 
 
14c46f5
4bbef93
 
 
14c46f5
 
 
 
4bbef93
 
 
14c46f5
4bbef93
14c46f5
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
import gradio as gr
from PIL import Image
import io
import zipfile

def resize_image_to_multiple_of_64_rgba(img: Image.Image):
    w, h = img.size
    # 计算最接近的 64 倍数(保底64)
    w64 = max(64, round(w / 64) * 64)
    h64 = max(64, round(h / 64) * 64)
    
    # 按最小缩放比,使原图能贴进 w64 x h64
    scale = min(w64 / w, h64 / h)
    new_width = int(w * scale)
    new_height = int(h * scale)
    
    # 用 RGBA、黑色背景(全不透明)
    background = Image.new("RGBA", (w64, h64), (0, 0, 0, 255))
    # 缩放原图到 new_width, new_height
    scaled_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
    
    # 贴到背景中央,保留其透明度
    offset_x = (w64 - new_width) // 2
    offset_y = (h64 - new_height) // 2
    background.paste(scaled_img, (offset_x, offset_y), scaled_img)
    return background

def make_collage_2x2(four_rgba_images):
    # 4 张图,均已是 RGBA、同尺寸
    w, h = four_rgba_images[0].size
    # 新画布:2x2 => 宽度 2*w,高度 2*h,背景黑
    collage = Image.new("RGBA", (2 * w, 2 * h), (0, 0, 0, 255))
    collage.paste(four_rgba_images[0], (0, 0), four_rgba_images[0])
    collage.paste(four_rgba_images[1], (w, 0), four_rgba_images[1])
    collage.paste(four_rgba_images[2], (0, h), four_rgba_images[2])
    collage.paste(four_rgba_images[3], (w, h), four_rgba_images[3])
    
    cw, ch = collage.size
    # 若合并后任意边 > 2048,则等比例缩小
    if cw > 2048 or ch > 2048:
        scale = min(2048 / cw, 2048 / ch)
        new_cw = int(cw * scale)
        new_ch = int(ch * scale)
        collage = collage.resize((new_cw, new_ch), Image.Resampling.LANCZOS)
    
    return collage

def process_images_for_preview(uploaded_files):
    pil_images = []
    for f in uploaded_files:
        if f is not None:
            # 以 RGBA 读图,保证可保留透明通道
            img = Image.open(f.name).convert("RGBA")
            pil_images.append(img)

    results = []
    # 每 4 张为一组,不足 4 张跳过
    for i in range(0, len(pil_images), 4):
        group = pil_images[i : i + 4]
        if len(group) < 4:
            break
        # 每张做64倍数 resize + 黑色背景填充
        resized = [resize_image_to_multiple_of_64_rgba(im) for im in group]
        # 再 2×2 拼接
        collage = make_collage_2x2(resized)
        results.append(collage)
    
    return results

def process_and_zip_for_download(uploaded_files):
    collages = process_images_for_preview(uploaded_files)
    if not collages:
        # 没有任何拼接图,就返回 None,让界面不显示可下载链接
        return None
    
    buf = io.BytesIO()
    with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
        for idx, img in enumerate(collages):
            # 转字节并写入 zip
            img_bytes = io.BytesIO()
            # 以 PNG 格式保存,带 RGBA
            img.save(img_bytes, format="PNG")
            img_bytes.seek(0)
            zf.writestr(f"collage_{idx+1}.png", img_bytes.read())
    buf.seek(0)
    return buf

with gr.Blocks() as demo:
    gr.Markdown("## 图片 2×2 拼接小工具")
    gr.Markdown(
        "1. 一次可上传多张图片,每 4 张为一组。\n"
        "2. 每组里 4 张图会先缩放到 64 的倍数、空余处用黑色填充,然后 2×2 拼接。\n"
        "3. 拼接结果若超过 2048×2048,则缩小到不超过 2048。\n"
        "4. 不足 4 张的剩余图片忽略。\n"
        "5. 支持保留 PNG 透明度,拼图时使用黑色背景。"
    )
    
    with gr.Row():
        with gr.Column():
            file_input = gr.Files(label="上传图片(可多选)", file_types=["image"])
            preview_btn = gr.Button("生成预览")
            download_btn = gr.Button("打包下载 ZIP")
        with gr.Column():
            # Gallery 构造上不使用 .style() 以兼容老版本
            gallery_output = gr.Gallery(label="拼接结果预览", columns=2)
            zip_output = gr.File(label="下载拼接结果 ZIP", interactive=False, visible=False)
    
    # 点击“生成预览” -> 返回拼接图列表
    preview_btn.click(
        fn=process_images_for_preview,
        inputs=[file_input],
        outputs=[gallery_output]
    )
    
    # 点击“打包下载 ZIP” -> 生成 zip
    download_btn.click(
        fn=process_and_zip_for_download,
        inputs=[file_input],
        outputs=[zip_output]
    )

demo.launch()