add internal holes detection in jump-hop image processing

This commit is contained in:
2026-06-08 21:32:44 +08:00
parent 49e4d085b3
commit 1dcba515b2

View File

@@ -38,6 +38,10 @@ pub struct GeneratedAssetSheetAlphaOptions {
pub key_color: GeneratedAssetSheetKeyColor,
pub remove_near_white_background: bool,
pub remove_disconnected_hard_key_background: bool,
// 中文注释:检测并清除被主体包围、不与画布四边连通的品红镂空区域。
// 仅对独立连通域整体判定,通过 min_pixels 过滤微小噪点。
pub detect_internal_holes: bool,
pub internal_hole_min_pixels: usize,
}
impl GeneratedAssetSheetAlphaOptions {
@@ -46,6 +50,8 @@ impl GeneratedAssetSheetAlphaOptions {
key_color: GeneratedAssetSheetKeyColor::GREEN_SCREEN,
remove_near_white_background: true,
remove_disconnected_hard_key_background: true,
detect_internal_holes: false,
internal_hole_min_pixels: 0,
}
}
@@ -54,6 +60,8 @@ impl GeneratedAssetSheetAlphaOptions {
key_color: GeneratedAssetSheetKeyColor::MAGENTA_SCREEN,
remove_near_white_background: false,
remove_disconnected_hard_key_background: false,
detect_internal_holes: true,
internal_hole_min_pixels: 16,
}
}
}
@@ -216,6 +224,66 @@ fn remove_generated_asset_sheet_green_screen_background(
}
}
// 中文注释:内部镂空洞检测——寻找与四边不连通、被主体包围的品红区域。
// 必须在软 matte 扩展之前执行,避免软扩展跨越窄前景通道误判。
if options.detect_internal_holes && options.internal_hole_min_pixels > 0 {
let mut hole_visited = vec![false; pixel_count];
let mut hole_queue = Vec::<usize>::new();
for start_index in 0..pixel_count {
if background_mask[start_index] != 0
|| key_scores[start_index] < GENERATED_ASSET_SHEET_GREEN_SCREEN_MIN_SCORE
|| hole_visited[start_index]
{
continue;
}
// 中文注释BFS 收集当前候选背景连通域
hole_queue.clear();
hole_queue.push(start_index);
hole_visited[start_index] = true;
let mut component = Vec::<usize>::new();
let mut touches_border = false;
let mut queue_cursor = 0usize;
while queue_cursor < hole_queue.len() {
let pixel_index = hole_queue[queue_cursor];
queue_cursor += 1;
component.push(pixel_index);
let x = pixel_index % width;
let y = pixel_index / width;
if x == 0 || x == width.saturating_sub(1) || y == 0 || y == height.saturating_sub(1) {
touches_border = true;
}
let neighbors = [
if x > 0 { Some(pixel_index - 1) } else { None },
if x + 1 < width { Some(pixel_index + 1) } else { None },
if y > 0 { Some(pixel_index - width) } else { None },
if y + 1 < height { Some(pixel_index + width) } else { None },
];
for next in neighbors.into_iter().flatten() {
if background_mask[next] != 0 || hole_visited[next] {
continue;
}
if key_scores[next] < GENERATED_ASSET_SHEET_GREEN_SCREEN_MIN_SCORE {
continue;
}
hole_visited[next] = true;
hole_queue.push(next);
}
}
if !touches_border && component.len() >= options.internal_hole_min_pixels {
for pixel_index in component {
background_mask[pixel_index] = 1;
}
}
}
}
let soft_green_cleanup_rounds = (width.min(height) / 40).clamp(4, 14);
for _ in 0..soft_green_cleanup_rounds {
let mut expanded_mask = background_mask.clone();