From 8a0dcf8be7f83441b3cd8f53dac18c518202851d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Wed, 9 Sep 2026 16:21:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=88=86=E7=A6=BB=E6=A0=87?= =?UTF-8?q?=E8=AE=B0=E6=A1=86=E7=9A=84=E5=8F=AF=E8=A7=86=E8=BE=A8=E8=AF=86?= =?UTF-8?q?=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为分离标记框增加两条绿色角到角交叉线。 新增 MARKER_LINE_WIDTH 常量统一配置标记线宽,并确保绘制结果裁剪在矩形内。 补充标记框像素级回归测试并同步更新自动分离专题文档。 --- .../ui_editor/commands/separation/marker.rs | 148 +++++++++++++++++- .../ui_editor/commands/separation/prompt.rs | 4 +- ...术方案】UI编辑器自动分离工作流-2026-09-08.md | 2 +- 3 files changed, 145 insertions(+), 9 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/separation/marker.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/separation/marker.rs index 292725416..9c8cfd120 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/separation/marker.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/separation/marker.rs @@ -3,6 +3,8 @@ use base64::Engine as _; use std::path::Path; use std::path::PathBuf; +const MARKER_LINE_WIDTH: u32 = 2; + pub async fn build_marked_image( source_url: String, nodes: Vec, @@ -120,12 +122,144 @@ fn draw_frame( return; }; let green = image::Rgba([0, 255, 0, 255]); - for xx in x0..=x1 { - image.put_pixel(xx, y0, green); - image.put_pixel(xx, y1, green); - } - for yy in y0..=y1 { - image.put_pixel(x0, yy, green); - image.put_pixel(x1, yy, green); + draw_line( + image, + (x0, y0), + (x1, y0), + green, + MARKER_LINE_WIDTH, + (x0, y0, x1, y1), + ); + draw_line( + image, + (x0, y1), + (x1, y1), + green, + MARKER_LINE_WIDTH, + (x0, y0, x1, y1), + ); + draw_line( + image, + (x0, y0), + (x0, y1), + green, + MARKER_LINE_WIDTH, + (x0, y0, x1, y1), + ); + draw_line( + image, + (x1, y0), + (x1, y1), + green, + MARKER_LINE_WIDTH, + (x0, y0, x1, y1), + ); + draw_line( + image, + (x0, y0), + (x1, y1), + green, + MARKER_LINE_WIDTH, + (x0, y0, x1, y1), + ); + draw_line( + image, + (x1, y0), + (x0, y1), + green, + MARKER_LINE_WIDTH, + (x0, y0, x1, y1), + ); +} + +fn draw_line( + image: &mut image::RgbaImage, + start: (u32, u32), + end: (u32, u32), + color: image::Rgba, + line_width: u32, + bounds: (u32, u32, u32, u32), +) { + let mut x = start.0 as i64; + let mut y = start.1 as i64; + let target_x = end.0 as i64; + let target_y = end.1 as i64; + let dx = (target_x - x).abs(); + let sx = if x < target_x { 1 } else { -1 }; + let dy = -(target_y - y).abs(); + let sy = if y < target_y { 1 } else { -1 }; + let mut error = dx + dy; + + loop { + draw_brush(image, x, y, color, line_width, bounds); + if x == target_x && y == target_y { + break; + } + let twice_error = error * 2; + if twice_error >= dy { + error += dy; + x += sx; + } + if twice_error <= dx { + error += dx; + y += sy; + } + } +} + +fn draw_brush( + image: &mut image::RgbaImage, + x: i64, + y: i64, + color: image::Rgba, + line_width: u32, + bounds: (u32, u32, u32, u32), +) { + let (x0, y0, x1, y1) = bounds; + let line_width = line_width.max(1) as i64; + let before = (line_width - 1) / 2; + let after = line_width / 2; + let min_x = (x - before).max(x0 as i64); + let max_x = (x + after).min(x1 as i64); + let min_y = (y - before).max(y0 as i64); + let max_y = (y + after).min(y1 as i64); + for yy in min_y..=max_y { + for xx in min_x..=max_x { + image.put_pixel(xx as u32, yy as u32, color); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn draw_frame_adds_green_cross_corner_lines() { + let mut image = image::RgbaImage::from_pixel(8, 6, image::Rgba([1, 2, 3, 255])); + draw_frame(&mut image, 1, 1, 5, 3, 8, 6); + let green = image::Rgba([0, 255, 0, 255]); + + for &(x, y) in &[(1, 1), (5, 1), (1, 3), (5, 3), (3, 2)] { + assert_eq!(*image.get_pixel(x, y), green, "pixel ({x}, {y})"); + } + assert_eq!(*image.get_pixel(3, 1), green); + assert_eq!(*image.get_pixel(3, 3), green); + assert_eq!(*image.get_pixel(2, 2), green); + assert_eq!(*image.get_pixel(4, 2), green); + assert_eq!(*image.get_pixel(0, 0), image::Rgba([1, 2, 3, 255])); + } + + #[test] + fn draw_frame_keeps_cross_inside_clipped_rect() { + let mut image = image::RgbaImage::from_pixel(4, 4, image::Rgba([1, 2, 3, 255])); + draw_frame(&mut image, 2, 2, 4, 4, 4, 4); + let green = image::Rgba([0, 255, 0, 255]); + for y in 2..4 { + for x in 2..4 { + assert_eq!(*image.get_pixel(x, y), green); + } + } + assert_eq!(*image.get_pixel(1, 1), image::Rgba([1, 2, 3, 255])); } } diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/separation/prompt.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/separation/prompt.rs index 7781b6bf0..d6df93e9f 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/separation/prompt.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/separation/prompt.rs @@ -5,8 +5,10 @@ const SHARED_SEPARATION_REQ: &str = r#" MUST hard edges; preserve no glow/blur beyond the exact visible shape. NEVER keep its parent's background with it. - UI elements that needs to extract has been marked with GREEN line frames (only for mark purpose, NEVER wrap a frame in your extraction). + UI elements that needs to extract has been marked with GREEN line frames box with crossline inside. (only for mark purpose, NEVER wrap a frame in your extraction). On some UI elements, there is some PURPLE filled area, they were removed UI elements, reconstruct the background under where they were. + + MUST extract exactly these marked UI elements area. "#; pub(super) fn gen_extract_prompt(separation_notes: Vec) -> String { let extract_system_prompt = format!( diff --git a/docs/technical/【技术方案】UI编辑器自动分离工作流-2026-09-08.md b/docs/technical/【技术方案】UI编辑器自动分离工作流-2026-09-08.md index 67b8a66a6..0c87b1cdc 100644 --- a/docs/technical/【技术方案】UI编辑器自动分离工作流-2026-09-08.md +++ b/docs/technical/【技术方案】UI编辑器自动分离工作流-2026-09-08.md @@ -29,7 +29,7 @@ ## 图片编辑与视觉绑定 -- image-edit 使用源 UI design 图片及由 Rust 生成的绿色标记/紫色重建输入。处理父节点时,紫色填充其 children 的矩形区域(包括已 problematic 的 children),再在父节点自身外围绘制绿色框;绿色框覆盖在紫色之上。叶节点只绘制绿色框,不填充自身。 +- image-edit 使用源 UI design 图片及由 Rust 生成的绿色标记/紫色重建输入。处理父节点时,紫色填充其 children 的矩形区域(包括已 problematic 的 children),再在父节点自身外围绘制绿色框和角到角的绿色交叉线;绿色标记覆盖在紫色之上。叶节点只绘制绿色框和角到角的绿色交叉线,不填充自身。 - 请求尺寸始终使用源 UI design 尺寸;Raw GPT Image 2 API 保证返回相同尺寸,客户端不额外做尺寸拒绝检查。 - 标记图构建、处理图解码/写入和 cut 裁切属于本地 CPU/文件操作,放入独立的 `spawn_blocking` 任务;image-edit 与 visual binding 网络请求仍运行在 async future 中。