dseditor's picture
Upload app.py
724aa84 verified
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)