dseditor's picture
Upload app.py
724aa84 verified
Raw
History Blame Contribute Delete
7.82 kB
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)