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

def resize_image_to_multiple_of_64(img: Image.Image, fill_color=(0, 0, 0)):
    """
    将单张图片缩放到 (w', h'),其中 w'、h' 均为 64 的整数倍。
    若原图宽高比与 w'/h' 不完全一致,则在空余处用 fill_color 填充(不裁切)。
    """
    w, h = img.size
    
    # 计算最接近的 64 倍数
    # 若 round(...) 结果为 0,至少保底 64
    w64 = max(64, round(w / 64) * 64)
    h64 = max(64, round(h / 64) * 64)
    
    # 根据最小缩放策略,保证图片能放进 w64 × h64
    scale = min(w64 / w, h64 / h)
    new_width = int(w * scale)
    new_height = int(h * scale)
    
    # 创建背景
    new_img = Image.new("RGB", (w64, h64), fill_color)
    # 将原图缩放
    scaled_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
    # 居中贴到背景上
    offset_x = (w64 - new_width) // 2
    offset_y = (h64 - new_height) // 2
    new_img.paste(scaled_img, (offset_x, offset_y))
    return new_img

def make_collage(four_images, fill_color=(0, 0, 0)):
    """
    将 4 张同样尺寸的图片(2×2)拼接成一张大图。
    如果结果大于 2048×2048,则等比例缩小到不超过 2048。
    """
    assert len(four_images) == 4, "必须是 4 张图"
    w, h = four_images[0].size  # 假设 4 张图尺寸相同
    collage = Image.new("RGB", (2 * w, 2 * h), fill_color)
    # 依次贴上
    collage.paste(four_images[0], (0, 0))
    collage.paste(four_images[1], (w, 0))
    collage.paste(four_images[2], (0, h))
    collage.paste(four_images[3], (w, h))
    
    cw, ch = collage.size
    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(uploaded_files):
    """
    处理用户上传的多张图片:
    1. 每组 4 张图 -> 统一到 64 的倍数尺寸 -> 拼成 2×2 的大图
    2. 大于 2048×2048 则缩小
    3. 返回多张拼接图(以列表的形式)供 Gradio 显示
    4. 若最后一组不足 4 张,则跳过
    """
    # 读取成 PIL 对象
    pil_images = []
    for file_obj in uploaded_files:
        if file_obj is not None:
            img = Image.open(file_obj.name).convert("RGB")
            pil_images.append(img)
    
    results = []
    # 以 4 张为一组进行处理
    for i in range(0, len(pil_images), 4):
        group = pil_images[i : i + 4]
        if len(group) < 4:
            # 不足 4 张,跳过
            break
        
        # 分别 resize
        resized = [resize_image_to_multiple_of_64(img, fill_color=(0,0,0)) for img in group]
        # 拼接
        collage = make_collage(resized, fill_color=(0,0,0))
        results.append(collage)
    
    return results

def process_and_zip(uploaded_files):
    """
    在内存中创建 Zip,将所有生成的拼接图打包。
    返回一个 BytesIO 给 Gradio 的 File 输出,用于下载。
    """
    # 先按需求生成拼接结果
    collages = process_images(uploaded_files)
    
    # 如果没有生成任何拼接图,则返回 None,让前端不显示下载
    if not collages:
        return None
    
    # 在内存中创建 zip
    buf = io.BytesIO()
    with zipfile.ZipFile(buf, "w") as zf:
        for idx, img in enumerate(collages):
            # 将 PIL 图像转成字节并写入 zip
            img_bytes = io.BytesIO()
            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

########################################
#        构建 Gradio 界面
########################################
css_custom = """
/* 你可根据需要,在此自定义或覆盖 Gradio 的 CSS 样式 */
"""

with gr.Blocks(css=css_custom) as demo:
    gr.Markdown("## 图片 2×2 拼接小工具")
    gr.Markdown(
        "1. 请选择若干张图片(数量需为 4 的倍数才能完整拼接)。<br>"
        "2. 每 4 张会被缩放并以 2×2 的形式合并成新图。<br>"
        "3. 如果合并后图像尺寸超过 2048×2048,会自动缩小。<br>"
        "4. 你可以在下方预览结果或打包下载。"
    )
    
    with gr.Row():
        with gr.Column():
            file_input = gr.Files(label="上传图片(可多选)", file_types=["image"])
            btn_preview = gr.Button("生成预览")
            btn_zip = gr.Button("打包下载 ZIP")
        
        with gr.Column():
            gallery_output = gr.Gallery(label="拼接结果预览").style(grid=[2], height="auto")
            zip_file = gr.File(label="下载拼接结果 ZIP", interactive=False, visible=False)
    
    # 点击“生成预览” -> 显示拼接好的图片(可能多张)
    btn_preview.click(
        fn=process_images,
        inputs=[file_input],
        outputs=[gallery_output]
    )
    
    # 点击“打包下载 ZIP” -> 生成 zip 文件供下载
    btn_zip.click(
        fn=process_and_zip,
        inputs=[file_input],
        outputs=[zip_file]
    )

demo.launch()