import numpy as np from typing import Tuple from .mask_utils import calculate_mask, expand_mask import os from PIL import Image def find_best_size_and_position(main_mask: np.ndarray, image_content: str, padding: int, mode: str = "side", chart_bbox: dict = None, avoid_mask: np.ndarray = None) -> Tuple[int, int, int]: """ 通过降采样加速查找最佳图片尺寸和位置 Args: main_mask: 主要内容的mask image_content: base64图片内容 padding: 边界padding mode: 放置模式,可选"side"、"background"或"overlay" chart_bbox: 图表边界框,格式为{"x": x, "y": y, "width": width, "height": height} avoid_mask: 需要避免重叠的区域mask Returns: Tuple[int, int, int]: (image_size, best_x, best_y) """ # Save the main_mask to PNG for debugging os.makedirs('tmp', exist_ok=True) mask_image = Image.fromarray((main_mask * 255).astype(np.uint8)) mask_image.save('tmp/main_mask.png') grid_size = 5 # 将main_mask降采样到1/grid_size大小 h, w = main_mask.shape downsampled_h = h // grid_size downsampled_w = w // grid_size downsampled_main = np.zeros((downsampled_h, downsampled_w), dtype=np.uint8) # 对每个grid进行降采样,只要原grid中有内容(1)就标记为1 for i in range(downsampled_h): for j in range(downsampled_w): y_start = max(0, (i - 1) * (grid_size)) x_start = max(0, (j - 1) * (grid_size)) y_end = min((i + 2) * (grid_size), h) x_end = min((j + 2) * (grid_size), w) grid = main_mask[y_start:y_end, x_start:x_end] downsampled_main[i, j] = 1 if np.any(grid == 1) else 0 # 如果有avoid_mask,也进行降采样 downsampled_avoid = None if avoid_mask is not None: downsampled_avoid = np.zeros((downsampled_h, downsampled_w), dtype=np.uint8) for i in range(downsampled_h): for j in range(downsampled_w): y_start = max(0, (i - 1) * (grid_size)) x_start = max(0, (j - 1) * (grid_size)) y_end = min((i + 2) * (grid_size), h) x_end = min((j + 2) * (grid_size), w) grid = avoid_mask[y_start:y_end, x_start:x_end] downsampled_avoid[i, j] = 1 if np.any(grid == 1) else 0 # 调整padding到降采样尺度 downsampled_padding = max(1, padding // grid_size) # 二分查找最佳尺寸 min_size = max(1, 64 // grid_size) # 最小尺寸也要降采样 max_size = int(min(downsampled_main.shape) * 1) best_size = min_size best_x = downsampled_padding best_y = downsampled_padding if mode == "side": best_overlap_ratio = float('inf') elif mode == "background": best_overlap_ratio = float('inf') else: best_overlap_ratio = 0 overlap_threshold = 0.01 if mode == "side": overlap_threshold = 0.01 elif mode == "background": overlap_threshold = 0.05 elif mode == "overlay": overlap_threshold = 0.97 while max_size - min_size >= 2: # 由于降采样,可以用更小的阈值 mid_size = (min_size + max_size) // 2 # 生成当前尺寸的图片mask并降采样 original_size = mid_size * grid_size temp_svg = f""" """ image_mask = calculate_mask(temp_svg, original_size, original_size, 0, grid_size=grid_size, bg_threshold=240) if mode == "background": image_mask = expand_mask(image_mask, 10) # Save the original image mask to PNG for debugging os.makedirs('tmp', exist_ok=True) mask_image = Image.fromarray((image_mask * 255).astype(np.uint8)) mask_image.save('tmp/image_mask.png') # 将image_mask降采样 downsampled_image = np.zeros((mid_size, mid_size), dtype=np.uint8) for i in range(mid_size): for j in range(mid_size): y_start = max(0, (i - 1) * (grid_size)) x_start = max(0, (j - 1) * (grid_size)) y_end = min((i + 2) * (grid_size), original_size) x_end = min((j + 2) * (grid_size), original_size) grid = image_mask[y_start:y_end, x_start:x_end] downsampled_image[i, j] = 1 if np.any(grid == 1) else 0 # 计算有效的搜索范围 if mode == "background" and chart_bbox is not None: # 将chart_bbox转换到降采样尺度 chart_x = max(0, chart_bbox["x"] // grid_size) chart_y = max(0, chart_bbox["y"] // grid_size) chart_width = min(chart_bbox["width"] // grid_size, downsampled_w - chart_x) chart_height = min(chart_bbox["height"] // grid_size, downsampled_h - chart_y) # 确保搜索范围在chart_bbox内 y_range = chart_height - mid_size - downsampled_padding * 2 x_range = chart_width - mid_size - downsampled_padding * 2 if y_range <= 0 or x_range <= 0: max_size = mid_size - 1 continue else: y_range = downsampled_h - mid_size - downsampled_padding * 2 x_range = downsampled_w - mid_size - downsampled_padding * 2 if y_range <= 0 or x_range <= 0: max_size = mid_size - 1 continue # 在降采样空间中寻找最佳位置 min_overlap = float('inf') if mode == "side" or mode == "background": min_overlap = float('inf') elif mode == "overlay": min_overlap = 0 current_x = downsampled_padding current_y = downsampled_padding min_distance = float('inf') mask_center_x = np.mean(np.where(downsampled_main == 1)[1]) if np.any(downsampled_main == 1) else downsampled_w // 2 mask_center_y = np.mean(np.where(downsampled_main == 1)[0]) if np.any(downsampled_main == 1) else downsampled_h // 2 if mode == "background" and chart_bbox is not None: y_start = chart_y + downsampled_padding y_end = chart_y + chart_height - mid_size - downsampled_padding + 1 x_start = chart_x + downsampled_padding x_end = chart_x + chart_width - mid_size - downsampled_padding + 1 else: y_start = downsampled_padding y_end = downsampled_h - mid_size - downsampled_padding + 1 x_start = downsampled_padding x_end = downsampled_w - mid_size - downsampled_padding + 1 for y in range(y_start, y_end): for x in range(x_start, x_end): region = downsampled_main[y:y + mid_size, x:x + mid_size] overlap = np.sum((region == 1) & (downsampled_image == 1)) total = np.sum(downsampled_image == 1) overlap_ratio = overlap / total if total > 0 else 1.0 # 检查与avoid_mask的重叠 avoid_overlap = 0 if downsampled_avoid is not None: avoid_region = downsampled_avoid[y:y + mid_size, x:x + mid_size] avoid_overlap = np.sum((avoid_region == 1) & (downsampled_image == 1)) if mode == "side" or mode == "background": if mode == "background" and chart_bbox is not None: distance_to_left = x - (chart_x + downsampled_padding) distance_to_right = (chart_x + chart_width - mid_size - downsampled_padding) - x distance_to_top = y - (chart_y + downsampled_padding) distance_to_bottom = (chart_y + chart_height - mid_size - downsampled_padding) - y else: distance_to_left = x - downsampled_padding distance_to_right = downsampled_w - mid_size - downsampled_padding - x distance_to_top = y - downsampled_padding distance_to_bottom = downsampled_h - mid_size - downsampled_padding - y distance_to_border = min(distance_to_left, distance_to_right, distance_to_top, distance_to_bottom) if overlap_ratio < min_overlap or (overlap_ratio < overlap_threshold and distance_to_border < min_distance): min_overlap = overlap_ratio current_x = x current_y = y min_distance = distance_to_border elif mode == "overlay": # 对于overlay模式,需要同时满足与main_mask的重叠足够大,且与avoid_mask没有重叠 if avoid_overlap > 0: continue # 跳过与avoid_mask有重叠的位置 distance_to_center = np.sqrt(((x + mid_size / 2 - mask_center_x) ** 2 + (y + mid_size / 2 - mask_center_y) ** 2)) if overlap_ratio > min_overlap or (overlap_ratio > overlap_threshold and distance_to_center < min_distance): min_overlap = overlap_ratio current_x = x current_y = y min_distance = distance_to_center # print(f"Trying size {mid_size * grid_size}x{mid_size * grid_size}, minimum overlap ratio: {min_overlap:.3f}") if mode == "side" or mode == "background": if min_overlap < overlap_threshold: best_size = mid_size best_overlap_ratio = min_overlap best_x = current_x best_y = current_y min_size = mid_size + 1 else: max_size = mid_size - 1 elif mode == "overlay": if min_overlap > overlap_threshold: best_size = mid_size best_overlap_ratio = min_overlap best_x = current_x best_y = current_y min_size = mid_size + 1 else: max_size = mid_size - 1 if best_overlap_ratio > overlap_threshold and (mode == "side" or mode == "background"): return 0, 0, 0 if best_overlap_ratio < overlap_threshold and mode == "overlay": return 0, 0, 0 final_size = best_size * grid_size final_x = best_x * grid_size final_y = best_y * grid_size ''' # 生成最终尺寸的图片mask temp_svg = f""" """ final_image_mask = calculate_mask(temp_svg, final_size, final_size, 0) # 创建合并的mask,将image_mask放在正确的位置 combined_mask = np.zeros_like(main_mask) combined_mask[main_mask == 1] = 1 # 将image_mask放在正确的位置 combined_mask[final_y:final_y + final_size, final_x:final_x + final_size] = np.where(final_image_mask == 1, 2, combined_mask[final_y:final_y + final_size, final_x:final_x + final_size]) # 保存合并的mask combined_image = Image.fromarray((combined_mask * 127).astype(np.uint8)) combined_image.save('tmp/all_mask.png') print(f"Final result: size={final_size}x{final_size}, position=({final_x}, {final_y}), overlap ratio={best_overlap_ratio:.3f}") ''' return final_size, final_x, final_y