服装单品组装增加外边距5像素
This commit is contained in:
@@ -44,6 +44,9 @@ def merge_images_to_square(outfit_items: List[Dict[str, str]], max_len=9, add_te
|
|||||||
# Define the final canvas size
|
# Define the final canvas size
|
||||||
CANVAS_SIZE = 1024
|
CANVAS_SIZE = 1024
|
||||||
|
|
||||||
|
# 定义每个 item 的外边距
|
||||||
|
MARGIN = 5 # 5像素外边距
|
||||||
|
|
||||||
# 1. Create the final white canvas
|
# 1. Create the final white canvas
|
||||||
# Using 'RGB' mode for JPG output
|
# Using 'RGB' mode for JPG output
|
||||||
canvas = Image.new('RGB', (CANVAS_SIZE, CANVAS_SIZE), 'white')
|
canvas = Image.new('RGB', (CANVAS_SIZE, CANVAS_SIZE), 'white')
|
||||||
@@ -88,6 +91,9 @@ def merge_images_to_square(outfit_items: List[Dict[str, str]], max_len=9, add_te
|
|||||||
# Get the correct list of target areas based on the number of valid images
|
# Get the correct list of target areas based on the number of valid images
|
||||||
target_areas = quadrants.get(num_images, [])
|
target_areas = quadrants.get(num_images, [])
|
||||||
|
|
||||||
|
if not target_areas:
|
||||||
|
raise ValueError(f"No layout defined for {num_images} images.")
|
||||||
|
|
||||||
# 4. Resize and Paste
|
# 4. Resize and Paste
|
||||||
for i, (img, item) in enumerate(zip(valid_images, outfit_items)):
|
for i, (img, item) in enumerate(zip(valid_images, outfit_items)):
|
||||||
item_id = item['item_id']
|
item_id = item['item_id']
|
||||||
@@ -96,13 +102,24 @@ def merge_images_to_square(outfit_items: List[Dict[str, str]], max_len=9, add_te
|
|||||||
# This should not happen if num_images <= 4
|
# This should not happen if num_images <= 4
|
||||||
break
|
break
|
||||||
|
|
||||||
# Target area dimensions (x_start, y_start, width, height)
|
# 原始目标区域 (x_start, y_start, width, height)
|
||||||
x_start, y_start, target_w, target_h = target_areas[i]
|
orig_x_start, orig_y_start, orig_w, orig_h = target_areas[i]
|
||||||
|
|
||||||
|
# 📢 应用边距:实际用于图像和文本的区域
|
||||||
|
# 新的起始点:向内移动 MARGIN
|
||||||
|
x_start = orig_x_start + MARGIN
|
||||||
|
y_start = orig_y_start + MARGIN
|
||||||
|
|
||||||
|
# 新的宽高:减去两倍的 MARGIN (左右/上下)
|
||||||
|
target_w = orig_w - 2 * MARGIN
|
||||||
|
target_h = orig_h - 2 * MARGIN
|
||||||
|
|
||||||
|
# --- 图像缩放与居中 ---
|
||||||
|
|
||||||
# Calculate new size while maintaining aspect ratio
|
# Calculate new size while maintaining aspect ratio
|
||||||
original_w, original_h = img.size
|
original_w, original_h = img.size
|
||||||
|
|
||||||
# Calculate the ratio needed to fit within the target area
|
# Calculate the ratio needed to fit within the *带边距的* 目标区域
|
||||||
ratio_w = target_w / original_w
|
ratio_w = target_w / original_w
|
||||||
ratio_h = target_h / original_h
|
ratio_h = target_h / original_h
|
||||||
|
|
||||||
@@ -113,45 +130,46 @@ def merge_images_to_square(outfit_items: List[Dict[str, str]], max_len=9, add_te
|
|||||||
new_w = int(original_w * resize_ratio)
|
new_w = int(original_w * resize_ratio)
|
||||||
new_h = int(original_h * resize_ratio)
|
new_h = int(original_h * resize_ratio)
|
||||||
|
|
||||||
# Resize the image. Image.Resampling.LANCZOS provides high-quality scaling.
|
# Resize the image.
|
||||||
# Pillow documentation recommends ANTIALIAS or BICUBIC for downscaling,
|
|
||||||
# but LANCZOS is a good general high-quality filter.
|
|
||||||
# Note: In Pillow versions > 9.0.0, Image.LANCZOS is now Image.Resampling.LANCZOS
|
|
||||||
resized_img = img.resize((new_w, new_h), Image.Resampling.LANCZOS)
|
resized_img = img.resize((new_w, new_h), Image.Resampling.LANCZOS)
|
||||||
|
|
||||||
# Calculate the paste position to center the resized image within its target area
|
# Calculate the paste position to center the resized image within its target area
|
||||||
# Center X: (Target Width - New Width) / 2 + X Start
|
# Center X: (Target Width - New Width) / 2 + X Start (带边距的 X_start)
|
||||||
paste_x = (target_w - new_w) // 2 + x_start
|
paste_x = (target_w - new_w) // 2 + x_start
|
||||||
# Center Y: (Target Height - New Height) / 2 + Y Start
|
|
||||||
# paste_y = (target_h - new_h) // 2 + y_start
|
|
||||||
|
|
||||||
|
# 预留文本高度 ( TEXT_RESERVE_HEIGHT )
|
||||||
TEXT_RESERVE_HEIGHT = 30
|
TEXT_RESERVE_HEIGHT = 30
|
||||||
|
|
||||||
|
# Center Y: (Target Height - New Height - 预留文本高度) / 2 + Y Start (带边距的 Y_start)
|
||||||
paste_y = (target_h - new_h - TEXT_RESERVE_HEIGHT) // 2 + y_start
|
paste_y = (target_h - new_h - TEXT_RESERVE_HEIGHT) // 2 + y_start
|
||||||
|
# 确保图片顶部不超出目标区域的 Y_start
|
||||||
paste_y = max(paste_y, y_start)
|
paste_y = max(paste_y, y_start)
|
||||||
|
|
||||||
# Paste the resized image onto the canvas
|
# Paste the resized image onto the canvas
|
||||||
canvas.paste(resized_img, (paste_x, paste_y))
|
canvas.paste(resized_img, (paste_x, paste_y))
|
||||||
|
|
||||||
|
# --- 文本居中与定位 ---
|
||||||
|
|
||||||
full_text = f"ID: {item_id}, Category: {category}"
|
full_text = f"ID: {item_id}, Category: {category}"
|
||||||
try:
|
|
||||||
# 推荐使用:计算文本的实际尺寸 (width, height)
|
|
||||||
bbox = draw.textbbox((0, 0), full_text, font=font)
|
|
||||||
text_w = bbox[2] - bbox[0]
|
|
||||||
text_h = bbox[3] - bbox[1]
|
|
||||||
except AttributeError:
|
|
||||||
# 兼容旧版本 Pillow
|
|
||||||
text_w, text_h = draw.textsize(full_text, font=font)
|
|
||||||
|
|
||||||
# 计算 X 轴起始位置:使其在目标区域 (target_w) 中居中
|
|
||||||
text_x_center = x_start + target_w // 2
|
|
||||||
text_x_start = text_x_center - text_w // 2
|
|
||||||
|
|
||||||
# 计算 Y 轴起始位置:将其放在目标区域的底部
|
|
||||||
# (目标区域的起始Y + 目标区域的高度 - 文本行的高度)
|
|
||||||
text_y_start = y_start + target_h - text_h - 5 # 减去 5 像素作为边距
|
|
||||||
|
|
||||||
# 3. 绘制合并后的文本
|
|
||||||
if add_text:
|
if add_text:
|
||||||
|
try:
|
||||||
|
# 推荐使用:计算文本的实际尺寸 (width, height)
|
||||||
|
bbox = draw.textbbox((0, 0), full_text, font=font)
|
||||||
|
text_w = bbox[2] - bbox[0]
|
||||||
|
text_h = bbox[3] - bbox[1]
|
||||||
|
except AttributeError:
|
||||||
|
# 兼容旧版本 Pillow
|
||||||
|
text_w, text_h = draw.textsize(full_text, font=font)
|
||||||
|
|
||||||
|
# 计算 X 轴起始位置:使其在 *带边距的目标区域* (target_w) 中居中
|
||||||
|
text_x_center = x_start + target_w // 2
|
||||||
|
text_x_start = text_x_center - text_w // 2
|
||||||
|
|
||||||
|
# 计算 Y 轴起始位置:将其放在 *带边距的目标区域* 的底部
|
||||||
|
# (带边距的起始Y + 带边距的高度 - 文本行的高度)
|
||||||
|
# 📢 在带边距的目标区域底部再减去 5 像素作为与底部的边距
|
||||||
|
text_y_start = y_start + target_h - text_h
|
||||||
|
|
||||||
draw.text((text_x_start, text_y_start),
|
draw.text((text_x_start, text_y_start),
|
||||||
full_text,
|
full_text,
|
||||||
fill='black',
|
fill='black',
|
||||||
@@ -161,3 +179,142 @@ def merge_images_to_square(outfit_items: List[Dict[str, str]], max_len=9, add_te
|
|||||||
# canvas.save(output_path, 'JPEG', quality=90)
|
# canvas.save(output_path, 'JPEG', quality=90)
|
||||||
|
|
||||||
return canvas
|
return canvas
|
||||||
|
|
||||||
|
# def merge_images_to_square(outfit_items: List[Dict[str, str]], max_len=9, add_text=True):
|
||||||
|
# """
|
||||||
|
# Loads up to 4 images from the given paths, resizes them while maintaining
|
||||||
|
# aspect ratio, and merges them onto a 1024x1024 white background JPG.
|
||||||
|
#
|
||||||
|
# The layout depends on the number of images:
|
||||||
|
# 1: Center the single image on the 1024x1024 canvas.
|
||||||
|
# 2: Place side-by-side, each scaled to fit a 512x1024 half.
|
||||||
|
# 3: Place in top-left (512x512), top-right (512x512), and bottom-left (512x512).
|
||||||
|
# 4: Place in all four 512x512 quadrants.
|
||||||
|
#
|
||||||
|
# Args:
|
||||||
|
# outfit_items: A list of item metadata (max length 9).
|
||||||
|
#
|
||||||
|
# Returns:
|
||||||
|
# The file path of the temporary merged JPG image.
|
||||||
|
# """
|
||||||
|
#
|
||||||
|
# # Define the final canvas size
|
||||||
|
# CANVAS_SIZE = 1024
|
||||||
|
#
|
||||||
|
# # 1. Create the final white canvas
|
||||||
|
# # Using 'RGB' mode for JPG output
|
||||||
|
# canvas = Image.new('RGB', (CANVAS_SIZE, CANVAS_SIZE), 'white')
|
||||||
|
# draw = ImageDraw.Draw(canvas)
|
||||||
|
# font = ImageFont.load_default()
|
||||||
|
#
|
||||||
|
# # 2. Define the quadrants/target areas (x, y, w, h)
|
||||||
|
# # The positions are based on a 512x512 quadrant size
|
||||||
|
# quadrants = {
|
||||||
|
# 1: [(0, 0, CANVAS_SIZE, CANVAS_SIZE)], # Single full-size placement
|
||||||
|
# 2: [(0, 0, 512, CANVAS_SIZE), (512, 0, 512, CANVAS_SIZE)], # Left, Right
|
||||||
|
# 3: [(0, 0, 512, 512), (512, 0, 512, 512), (0, 512, 512, 512)], # Top-Left, Top-Right, Bottom-Left
|
||||||
|
# 4: [(0, 0, 512, 512), (512, 0, 512, 512), (0, 512, 512, 512), (512, 512, 512, 512)], # All Four
|
||||||
|
# 5: ALL_9_CELLS[:5], # 布局前5个单元格 (1-5)
|
||||||
|
# 6: ALL_9_CELLS[:6], # 布局前6个单元格 (1-6)
|
||||||
|
# 7: ALL_9_CELLS[:7], # 布局前7个单元格 (1-7)
|
||||||
|
# 8: ALL_9_CELLS[:8], # 布局前8个单元格 (1-8)
|
||||||
|
# 9: ALL_9_CELLS[:9] # 布局全部9个单元格 (1-9)
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# # 3. Load and Filter Images
|
||||||
|
# valid_images = []
|
||||||
|
# image_paths = [item['image_path'] for item in outfit_items]
|
||||||
|
# for path in image_paths:
|
||||||
|
# try:
|
||||||
|
# # We use Image.open() and convert to 'RGB' to handle potential transparency (RGBA)
|
||||||
|
# # and ensure compatibility with the final 'RGB' canvas and JPG output.
|
||||||
|
# img = oss_get_image(oss_client=minio_client, path=f"{MINIO_LC_DATA_PATH}/{path}", data_type="PIL").convert('RGB')
|
||||||
|
# # img = Image.open(path).convert('RGB')
|
||||||
|
# valid_images.append(img)
|
||||||
|
# except Exception as e:
|
||||||
|
# logger.error(f"Error loading image {path}. Skipping: {e}")
|
||||||
|
#
|
||||||
|
# num_images = len(valid_images)
|
||||||
|
#
|
||||||
|
# if num_images == 0:
|
||||||
|
# raise ValueError("No valid images were loaded.")
|
||||||
|
#
|
||||||
|
# if num_images > max_len:
|
||||||
|
# raise ValueError(f"Valid item number {num_images} exceed max limit {max_len}")
|
||||||
|
#
|
||||||
|
# # Get the correct list of target areas based on the number of valid images
|
||||||
|
# target_areas = quadrants.get(num_images, [])
|
||||||
|
#
|
||||||
|
# # 4. Resize and Paste
|
||||||
|
# for i, (img, item) in enumerate(zip(valid_images, outfit_items)):
|
||||||
|
# item_id = item['item_id']
|
||||||
|
# category = item['category']
|
||||||
|
# if i >= len(target_areas):
|
||||||
|
# # This should not happen if num_images <= 4
|
||||||
|
# break
|
||||||
|
#
|
||||||
|
# # Target area dimensions (x_start, y_start, width, height)
|
||||||
|
# x_start, y_start, target_w, target_h = target_areas[i]
|
||||||
|
#
|
||||||
|
# # Calculate new size while maintaining aspect ratio
|
||||||
|
# original_w, original_h = img.size
|
||||||
|
#
|
||||||
|
# # Calculate the ratio needed to fit within the target area
|
||||||
|
# ratio_w = target_w / original_w
|
||||||
|
# ratio_h = target_h / original_h
|
||||||
|
#
|
||||||
|
# # Use the *smaller* of the two ratios to ensure the image fits entirely
|
||||||
|
# resize_ratio = min(ratio_w, ratio_h)
|
||||||
|
#
|
||||||
|
# # Calculate the new dimensions
|
||||||
|
# new_w = int(original_w * resize_ratio)
|
||||||
|
# new_h = int(original_h * resize_ratio)
|
||||||
|
#
|
||||||
|
# # Resize the image. Image.Resampling.LANCZOS provides high-quality scaling.
|
||||||
|
# # Pillow documentation recommends ANTIALIAS or BICUBIC for downscaling,
|
||||||
|
# # but LANCZOS is a good general high-quality filter.
|
||||||
|
# # Note: In Pillow versions > 9.0.0, Image.LANCZOS is now Image.Resampling.LANCZOS
|
||||||
|
# resized_img = img.resize((new_w, new_h), Image.Resampling.LANCZOS)
|
||||||
|
#
|
||||||
|
# # Calculate the paste position to center the resized image within its target area
|
||||||
|
# # Center X: (Target Width - New Width) / 2 + X Start
|
||||||
|
# paste_x = (target_w - new_w) // 2 + x_start
|
||||||
|
# # Center Y: (Target Height - New Height) / 2 + Y Start
|
||||||
|
# # paste_y = (target_h - new_h) // 2 + y_start
|
||||||
|
#
|
||||||
|
# TEXT_RESERVE_HEIGHT = 30
|
||||||
|
# paste_y = (target_h - new_h - TEXT_RESERVE_HEIGHT) // 2 + y_start
|
||||||
|
# paste_y = max(paste_y, y_start)
|
||||||
|
#
|
||||||
|
# # Paste the resized image onto the canvas
|
||||||
|
# canvas.paste(resized_img, (paste_x, paste_y))
|
||||||
|
#
|
||||||
|
# full_text = f"ID: {item_id}, Category: {category}"
|
||||||
|
# try:
|
||||||
|
# # 推荐使用:计算文本的实际尺寸 (width, height)
|
||||||
|
# bbox = draw.textbbox((0, 0), full_text, font=font)
|
||||||
|
# text_w = bbox[2] - bbox[0]
|
||||||
|
# text_h = bbox[3] - bbox[1]
|
||||||
|
# except AttributeError:
|
||||||
|
# # 兼容旧版本 Pillow
|
||||||
|
# text_w, text_h = draw.textsize(full_text, font=font)
|
||||||
|
#
|
||||||
|
# # 计算 X 轴起始位置:使其在目标区域 (target_w) 中居中
|
||||||
|
# text_x_center = x_start + target_w // 2
|
||||||
|
# text_x_start = text_x_center - text_w // 2
|
||||||
|
#
|
||||||
|
# # 计算 Y 轴起始位置:将其放在目标区域的底部
|
||||||
|
# # (目标区域的起始Y + 目标区域的高度 - 文本行的高度)
|
||||||
|
# text_y_start = y_start + target_h - text_h - 5 # 减去 5 像素作为边距
|
||||||
|
#
|
||||||
|
# # 3. 绘制合并后的文本
|
||||||
|
# if add_text:
|
||||||
|
# draw.text((text_x_start, text_y_start),
|
||||||
|
# full_text,
|
||||||
|
# fill='black',
|
||||||
|
# font=font)
|
||||||
|
#
|
||||||
|
# # Save as a high-quality JPG (quality=90 is a good balance)
|
||||||
|
# # canvas.save(output_path, 'JPEG', quality=90)
|
||||||
|
#
|
||||||
|
# return canvas
|
||||||
|
|||||||
Reference in New Issue
Block a user