File size: 7,822 Bytes
724aa84
5f6ef81
 
 
 
724aa84
 
5f6ef81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
724aa84
5f6ef81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59cbef2
 
 
 
 
 
 
 
 
 
 
 
 
5f6ef81
59cbef2
 
5f6ef81
 
59cbef2
5f6ef81
 
 
ecfbfd1
59cbef2
 
5f6ef81
 
59cbef2
5f6ef81
 
 
 
 
59cbef2
 
 
 
 
 
ecfbfd1
59cbef2
 
 
 
 
 
 
ecfbfd1
 
 
 
 
5f6ef81
 
 
724aa84
5f6ef81
724aa84
5f6ef81
 
 
 
ecfbfd1
5f6ef81
 
 
 
 
 
724aa84
ecfbfd1
724aa84
 
5f6ef81
 
 
 
ecfbfd1
 
724aa84
5f6ef81
724aa84
5f6ef81
 
 
724aa84
5f6ef81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ecfbfd1
5f6ef81
d935ae6
5f6ef81
 
 
ecfbfd1
5f6ef81
 
 
 
 
724aa84
5f6ef81
 
ecfbfd1
 
724aa84
5f6ef81
 
 
 
 
ecfbfd1
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

import gradio as gr
import numpy as np
from PIL import Image
import io
import tempfile
import os

def resize_images_to_match(images, resize_mode="height"):
    if not images or len(images) == 0:
        return []
    valid_images = [img for img in images if img is not None]
    if not valid_images:
        return []
    if resize_mode == "height":
        target_height = min(img.size[1] for img in valid_images)
        resized_images = []
        for img in valid_images:
            aspect_ratio = img.size[0] / img.size[1]
            new_width = int(target_height * aspect_ratio)
            resized_img = img.resize((new_width, target_height), Image.Resampling.LANCZOS)
            resized_images.append(resized_img)
    else:
        target_width = min(img.size[0] for img in valid_images)
        resized_images = []
        for img in valid_images:
            aspect_ratio = img.size[1] / img.size[0]
            new_height = int(target_width * aspect_ratio)
            resized_img = img.resize((target_width, new_height), Image.Resampling.LANCZOS)
            resized_images.append(resized_img)
    return resized_images

def concatenate_images(images, direction="horizontal", spacing=0, background_color="white"):
    if not images or len(images) == 0:
        return None
    valid_images = [img for img in images if img is not None]
    if not valid_images:
        return None
    rgb_images = []
    for img in valid_images:
        if img.mode != 'RGB':
            if img.mode in ('RGBA', 'LA'):
                background = Image.new('RGB', img.size, 'white')
                background.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None)
                rgb_images.append(background)
            else:
                rgb_images.append(img.convert('RGB'))
        else:
            rgb_images.append(img)
    if len(rgb_images) == 1:
        return rgb_images[0]
    if direction == "horizontal":
        total_width = sum(img.size[0] for img in rgb_images) + spacing * (len(rgb_images) - 1)
        max_height = max(img.size[1] for img in rgb_images)
        result = Image.new('RGB', (total_width, max_height), background_color)
        x_offset = 0
        for img in rgb_images:
            y_offset = (max_height - img.size[1]) // 2
            result.paste(img, (x_offset, y_offset))
            x_offset += img.size[0] + spacing
    else:
        max_width = max(img.size[0] for img in rgb_images)
        total_height = sum(img.size[1] for img in rgb_images) + spacing * (len(rgb_images) - 1)
        result = Image.new('RGB', (max_width, total_height), background_color)
        y_offset = 0
        for img in rgb_images:
            x_offset = (max_width - img.size[0]) // 2
            result.paste(img, (x_offset, y_offset))
            y_offset += img.size[1] + spacing
    return result

def save_as_jpg(image, quality=95):
    if image is None:
        return None
    if image.mode != 'RGB':
        if image.mode in ('RGBA', 'LA'):
            background = Image.new('RGB', image.size, 'white')
            background.paste(image, mask=image.split()[-1])
            image = background
        else:
            image = image.convert('RGB')
    buffer = io.BytesIO()
    image.save(buffer, format='JPEG', quality=quality, optimize=True)
    buffer.seek(0)
    return Image.open(buffer)

def process_images(
    image1, image2, image3, image4, image5, image6,
    resize_mode, concat_direction, spacing, bg_color, jpg_quality
):
    uploaded_images = [image1, image2, image3, image4, image5, image6]
    valid_images = [img for img in uploaded_images if img is not None]
    if len(valid_images) == 0:
        return None, "❌ 請至少上傳一張圖片", None
    if len(valid_images) == 1:
        return valid_images[0], f"✅ 只有一張圖片,無需拼接", None
    try:
        if resize_mode == "等高度 (水平拼接)":
            resized_images = resize_images_to_match(valid_images, "height")
            direction = "horizontal"
        else:
            resized_images = resize_images_to_match(valid_images, "width")
            direction = "vertical"
        if concat_direction != "自動 (根據調整模式)":
            direction = "horizontal" if concat_direction == "水平拼接" else "vertical"
        result = concatenate_images(resized_images, direction, spacing, bg_color)
        if result is None:
            return None, "❌ 拼接失敗", None
        result = save_as_jpg(result, quality=jpg_quality)
        temp_path = os.path.join(tempfile.gettempdir(), "result.jpg")
        result.save(temp_path, format="JPEG", quality=jpg_quality)
        info = f"✅ 成功拼接 {len(valid_images)} 張圖片\n"
        info += f"📐 最終尺寸: {result.size[0]} x {result.size[1]} 像素\n"
        info += f"🔄 調整模式: {resize_mode}\n"
        info += f"➡️ 拼接方向: {'水平' if direction == 'horizontal' else '垂直'}\n"
        info += f"📏 間距: {spacing} 像素\n"
        info += f"💾 輸出品質: {jpg_quality}%"
        return result, info, temp_path
    except Exception as e:
        return None, f"❌ 處理過程中出現錯誤: {str(e)}", None

def create_interface():
    with gr.Blocks(title="圖片拼接比較工具", theme=gr.themes.Soft()) as iface:
        gr.Markdown("# 🖼️ 圖片拼接比較工具")
        with gr.Row():
            with gr.Column(scale=2):
                gr.Markdown("### 📁 上傳圖片")
                with gr.Row():
                    image1 = gr.Image(type="pil", label="圖片 1")
                    image2 = gr.Image(type="pil", label="圖片 2")
                    image3 = gr.Image(type="pil", label="圖片 3")
                with gr.Row():
                    image4 = gr.Image(type="pil", label="圖片 4")
                    image5 = gr.Image(type="pil", label="圖片 5")
                    image6 = gr.Image(type="pil", label="圖片 6")
                gr.Markdown("### ⚙️ 處理設定")
                with gr.Row():
                    resize_mode = gr.Dropdown(
                        choices=["等高度 (水平拼接)", "等寬度 (垂直拼接)"],
                        value="等高度 (水平拼接)",
                        label="尺寸調整模式"
                    )
                    concat_direction = gr.Dropdown(
                        choices=["自動 (根據調整模式)", "水平拼接", "垂直拼接"],
                        value="自動 (根據調整模式)",
                        label="拼接方向"
                    )
                with gr.Row():
                    spacing = gr.Slider(0, 50, value=2, step=1, label="圖片間距 (像素)")
                    bg_color = gr.Dropdown(
                        choices=["white", "black", "gray"],
                        value="white",
                        label="背景顏色"
                    )
                jpg_quality = gr.Slider(60, 100, value=95, step=5, label="JPG 輸出品質 (%)")
                process_btn = gr.Button("🚀 開始處理", variant="primary", size="lg")
            with gr.Column(scale=2):
                gr.Markdown("### 🎯 處理結果")
                result_image = gr.Image(type="pil", label="拼接結果")
                result_info = gr.Textbox(label="處理信息", lines=6, max_lines=10)
                result_file = gr.File(label="下載 JPG 檔案")
        process_btn.click(
            fn=process_images,
            inputs=[image1, image2, image3, image4, image5, image6,
                    resize_mode, concat_direction, spacing, bg_color, jpg_quality],
            outputs=[result_image, result_info, result_file]
        )
    return iface

if __name__ == "__main__":
    app = create_interface()
    app.launch(server_name="0.0.0.0", server_port=7860, share=True)