格式化自动分离 Rust 模块
统一树选择、标记图和批次流程代码风格
This commit is contained in:
@@ -4,49 +4,129 @@ use image::GenericImage;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub async fn build_marked_image(source_url: String, nodes: Vec<SeparationNode>, target: PathBuf) -> Result<String, String> {
|
||||
pub async fn build_marked_image(
|
||||
source_url: String,
|
||||
nodes: Vec<SeparationNode>,
|
||||
target: PathBuf,
|
||||
) -> Result<String, String> {
|
||||
tokio::task::spawn_blocking(move || build_marked_image_blocking(&source_url, &nodes, &target))
|
||||
.await
|
||||
.map_err(|error| format!("构建标记图任务失败:{error}"))?
|
||||
}
|
||||
|
||||
fn build_marked_image_blocking(source_url: &str, nodes: &[SeparationNode], target: &Path) -> Result<String, String> {
|
||||
let encoded = source_url.split_once(',').map(|(_, d)| d).ok_or_else(|| "源图 data URL 无效".to_string())?;
|
||||
let bytes = base64::engine::general_purpose::STANDARD.decode(encoded).map_err(|e| format!("解码源图失败:{e}"))?;
|
||||
let mut image = image::load_from_memory(&bytes).map_err(|e| format!("读取源图失败:{e}"))?.to_rgba8();
|
||||
fn build_marked_image_blocking(
|
||||
source_url: &str,
|
||||
nodes: &[SeparationNode],
|
||||
target: &Path,
|
||||
) -> Result<String, String> {
|
||||
let encoded = source_url
|
||||
.split_once(',')
|
||||
.map(|(_, d)| d)
|
||||
.ok_or_else(|| "源图 data URL 无效".to_string())?;
|
||||
let bytes = base64::engine::general_purpose::STANDARD
|
||||
.decode(encoded)
|
||||
.map_err(|e| format!("解码源图失败:{e}"))?;
|
||||
let mut image = image::load_from_memory(&bytes)
|
||||
.map_err(|e| format!("读取源图失败:{e}"))?
|
||||
.to_rgba8();
|
||||
let width = image.width();
|
||||
let height = image.height();
|
||||
// Purple reconstruction comes first so the parent green frame remains visible on top.
|
||||
for node in nodes {
|
||||
for child in &node.children {
|
||||
fill_rect(&mut image, child.global_pos_x_px, child.global_pos_y_px, child.width_px, child.height_px, image::Rgba([180, 0, 180, 120]), width, height);
|
||||
fill_rect(
|
||||
&mut image,
|
||||
child.global_pos_x_px,
|
||||
child.global_pos_y_px,
|
||||
child.width_px,
|
||||
child.height_px,
|
||||
image::Rgba([180, 0, 180, 120]),
|
||||
width,
|
||||
height,
|
||||
);
|
||||
}
|
||||
}
|
||||
for node in nodes {
|
||||
draw_frame(&mut image, node.global_pos_x_px, node.global_pos_y_px, node.width_px, node.height_px, width, height);
|
||||
draw_frame(
|
||||
&mut image,
|
||||
node.global_pos_x_px,
|
||||
node.global_pos_y_px,
|
||||
node.width_px,
|
||||
node.height_px,
|
||||
width,
|
||||
height,
|
||||
);
|
||||
}
|
||||
image::DynamicImage::ImageRgba8(image.clone()).save_with_format(target, image::ImageFormat::Png).map_err(|e| format!("写入标记图失败:{e}"))?;
|
||||
image::DynamicImage::ImageRgba8(image.clone())
|
||||
.save_with_format(target, image::ImageFormat::Png)
|
||||
.map_err(|e| format!("写入标记图失败:{e}"))?;
|
||||
let mut png = Vec::new();
|
||||
image::DynamicImage::ImageRgba8(image).write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).map_err(|e| format!("编码标记图失败:{e}"))?;
|
||||
Ok(format!("data:image/png;base64,{}", base64::engine::general_purpose::STANDARD.encode(png)))
|
||||
image::DynamicImage::ImageRgba8(image)
|
||||
.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png)
|
||||
.map_err(|e| format!("编码标记图失败:{e}"))?;
|
||||
Ok(format!(
|
||||
"data:image/png;base64,{}",
|
||||
base64::engine::general_purpose::STANDARD.encode(png)
|
||||
))
|
||||
}
|
||||
|
||||
fn clipped_rect(x: u32, y: u32, w: u32, h: u32, width: u32, height: u32) -> Option<(u32, u32, u32, u32)> {
|
||||
if width == 0 || height == 0 || w == 0 || h == 0 { return None; }
|
||||
let x0 = x.min(width - 1); let y0 = y.min(height - 1);
|
||||
fn clipped_rect(
|
||||
x: u32,
|
||||
y: u32,
|
||||
w: u32,
|
||||
h: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Option<(u32, u32, u32, u32)> {
|
||||
if width == 0 || height == 0 || w == 0 || h == 0 {
|
||||
return None;
|
||||
}
|
||||
let x0 = x.min(width - 1);
|
||||
let y0 = y.min(height - 1);
|
||||
let x1 = x.saturating_add(w).min(width).saturating_sub(1);
|
||||
let y1 = y.saturating_add(h).min(height).saturating_sub(1);
|
||||
(x0 <= x1 && y0 <= y1).then_some((x0, y0, x1, y1))
|
||||
}
|
||||
|
||||
fn fill_rect(image: &mut image::RgbaImage, x: u32, y: u32, w: u32, h: u32, color: image::Rgba<u8>, width: u32, height: u32) {
|
||||
let Some((x0, y0, x1, y1)) = clipped_rect(x, y, w, h, width, height) else { return; };
|
||||
for yy in y0..=y1 { for xx in x0..=x1 { image.put_pixel(xx, yy, color); } }
|
||||
fn fill_rect(
|
||||
image: &mut image::RgbaImage,
|
||||
x: u32,
|
||||
y: u32,
|
||||
w: u32,
|
||||
h: u32,
|
||||
color: image::Rgba<u8>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) {
|
||||
let Some((x0, y0, x1, y1)) = clipped_rect(x, y, w, h, width, height) else {
|
||||
return;
|
||||
};
|
||||
for yy in y0..=y1 {
|
||||
for xx in x0..=x1 {
|
||||
image.put_pixel(xx, yy, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_frame(image: &mut image::RgbaImage, x: u32, y: u32, w: u32, h: u32, width: u32, height: u32) {
|
||||
let Some((x0, y0, x1, y1)) = clipped_rect(x, y, w, h, width, height) else { return; };
|
||||
fn draw_frame(
|
||||
image: &mut image::RgbaImage,
|
||||
x: u32,
|
||||
y: u32,
|
||||
w: u32,
|
||||
h: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) {
|
||||
let Some((x0, y0, x1, y1)) = clipped_rect(x, y, w, h, width, height) else {
|
||||
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); }
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
mod model;
|
||||
mod marker;
|
||||
mod model;
|
||||
mod persistence;
|
||||
mod prompt;
|
||||
mod tree;
|
||||
mod workflow;
|
||||
|
||||
pub(crate) use marker::build_marked_image;
|
||||
pub use model::*;
|
||||
pub use persistence::*;
|
||||
pub use tree::*;
|
||||
pub use workflow::apply_batch_patch;
|
||||
pub(crate) use workflow::separate_ui_impl;
|
||||
pub(crate) use marker::build_marked_image;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ui_editor::component::Component;
|
||||
use crate::ui_editor::layout::node::Node;
|
||||
use crate::ui_editor::state::{State, UITree};
|
||||
use crate::ui_editor::utils::{NodeId, UIDesignImageId};
|
||||
use std::path::Path;
|
||||
use crate::ui_editor::component::image::{ImageComponent, ImageType};
|
||||
use crate::ui_editor::component::Component;
|
||||
use crate::ui_editor::layout::children_display_mode::ChildrenDisplayMode;
|
||||
use crate::ui_editor::layout::control_layout::ControlLayout;
|
||||
use crate::ui_editor::layout::node::Node;
|
||||
use crate::ui_editor::layout::node::{NodeMetadata, NodeSource, StageStatus};
|
||||
use crate::ui_editor::resource::ui_design_image::UIDesignImage;
|
||||
use crate::ui_editor::state::{State, UITree};
|
||||
use crate::ui_editor::utils::{NodeId, UIDesignImageId};
|
||||
use nalgebra::Vector2;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use typed_floats::tf32::StrictlyPositiveFinite;
|
||||
|
||||
fn node(id: &str, components: Vec<Component>, children: Vec<Node>) -> Node {
|
||||
@@ -89,10 +89,7 @@ mod tests {
|
||||
)],
|
||||
);
|
||||
let result = construct_separation_state(&state(root));
|
||||
assert_eq!(
|
||||
result.trees[0].root.children[0].id.as_str(),
|
||||
"image"
|
||||
);
|
||||
assert_eq!(result.trees[0].root.children[0].id.as_str(), "image");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -7,11 +7,21 @@ use std::collections::HashSet;
|
||||
|
||||
fn is_unbound_image(node: &Node) -> bool {
|
||||
node.components.iter().any(|component| {
|
||||
matches!(component, Component::Image(ImageComponent { target_graphic: None, .. }))
|
||||
matches!(
|
||||
component,
|
||||
Component::Image(ImageComponent {
|
||||
target_graphic: None,
|
||||
..
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn node_pixel_rect(node: &Node, parent: &crate::ui_editor::layout::dimension::UIRect, ppu: f32) -> (u32, u32, u32, u32) {
|
||||
fn node_pixel_rect(
|
||||
node: &Node,
|
||||
parent: &crate::ui_editor::layout::dimension::UIRect,
|
||||
ppu: f32,
|
||||
) -> (u32, u32, u32, u32) {
|
||||
let rect = node.layout.transform.resolve(parent);
|
||||
(
|
||||
(rect.min.x * ppu).max(0.0).round() as u32,
|
||||
@@ -32,7 +42,12 @@ fn node_description(node: &Node) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_todo_nodes(node: &Node, parent: &crate::ui_editor::layout::dimension::UIRect, ppu: f32, output: &mut Vec<SeparationNode>) {
|
||||
fn collect_todo_nodes(
|
||||
node: &Node,
|
||||
parent: &crate::ui_editor::layout::dimension::UIRect,
|
||||
ppu: f32,
|
||||
output: &mut Vec<SeparationNode>,
|
||||
) {
|
||||
let rect = node.layout.transform.resolve(parent);
|
||||
let mut children = Vec::new();
|
||||
for child in &node.children {
|
||||
@@ -41,10 +56,17 @@ fn collect_todo_nodes(node: &Node, parent: &crate::ui_editor::layout::dimension:
|
||||
if is_unbound_image(node) {
|
||||
let (x, y, w, h) = node_pixel_rect(node, parent, ppu);
|
||||
output.push(SeparationNode {
|
||||
id: node.id.clone(), global_pos_x_px: x, global_pos_y_px: y,
|
||||
width_px: w, height_px: h,
|
||||
note: SeparationNote { description: node_description(node), text_note: String::new() },
|
||||
children, rework_count: 0,
|
||||
id: node.id.clone(),
|
||||
global_pos_x_px: x,
|
||||
global_pos_y_px: y,
|
||||
width_px: w,
|
||||
height_px: h,
|
||||
note: SeparationNote {
|
||||
description: node_description(node),
|
||||
text_note: String::new(),
|
||||
},
|
||||
children,
|
||||
rework_count: 0,
|
||||
});
|
||||
} else {
|
||||
output.extend(children);
|
||||
@@ -52,40 +74,71 @@ fn collect_todo_nodes(node: &Node, parent: &crate::ui_editor::layout::dimension:
|
||||
}
|
||||
|
||||
pub fn construct_separation_state(state: &State) -> SeparationState {
|
||||
let trees = state.ui_trees.iter().filter_map(|tree| {
|
||||
let image = state.ui_design_images.get(&tree.src_ui_design)?;
|
||||
let ppu = image.pixels_per_unit.get();
|
||||
let size = image.pixel_size / ppu;
|
||||
let root_rect = crate::ui_editor::layout::dimension::UIRect::new(nalgebra::Point2::origin(), size);
|
||||
let mut children = Vec::new();
|
||||
for child in &tree.root.children {
|
||||
collect_todo_nodes(child, &root_rect, ppu, &mut children);
|
||||
}
|
||||
let root_extractable = is_unbound_image(&tree.root);
|
||||
if !root_extractable && children.is_empty() { return None; }
|
||||
let (x, y, w, h) = node_pixel_rect(&tree.root, &root_rect, ppu);
|
||||
Some(SeparationTree {
|
||||
src_ui_design: tree.src_ui_design.clone(),
|
||||
root: SeparationNode {
|
||||
id: tree.root.id.clone(), global_pos_x_px: x, global_pos_y_px: y,
|
||||
width_px: w, height_px: h,
|
||||
note: SeparationNote { description: node_description(&tree.root), text_note: String::new() },
|
||||
children, rework_count: 0,
|
||||
},
|
||||
root_extractable,
|
||||
let trees = state
|
||||
.ui_trees
|
||||
.iter()
|
||||
.filter_map(|tree| {
|
||||
let image = state.ui_design_images.get(&tree.src_ui_design)?;
|
||||
let ppu = image.pixels_per_unit.get();
|
||||
let size = image.pixel_size / ppu;
|
||||
let root_rect =
|
||||
crate::ui_editor::layout::dimension::UIRect::new(nalgebra::Point2::origin(), size);
|
||||
let mut children = Vec::new();
|
||||
for child in &tree.root.children {
|
||||
collect_todo_nodes(child, &root_rect, ppu, &mut children);
|
||||
}
|
||||
let root_extractable = is_unbound_image(&tree.root);
|
||||
if !root_extractable && children.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let (x, y, w, h) = node_pixel_rect(&tree.root, &root_rect, ppu);
|
||||
Some(SeparationTree {
|
||||
src_ui_design: tree.src_ui_design.clone(),
|
||||
root: SeparationNode {
|
||||
id: tree.root.id.clone(),
|
||||
global_pos_x_px: x,
|
||||
global_pos_y_px: y,
|
||||
width_px: w,
|
||||
height_px: h,
|
||||
note: SeparationNote {
|
||||
description: node_description(&tree.root),
|
||||
text_note: String::new(),
|
||||
},
|
||||
children,
|
||||
rework_count: 0,
|
||||
},
|
||||
root_extractable,
|
||||
})
|
||||
})
|
||||
}).collect();
|
||||
SeparationState { schema_version: SEPARATION_STATE_SCHEMA_VERSION.to_string(), trees, bound: Vec::new(), problematic_nodes: Vec::new() }
|
||||
.collect();
|
||||
SeparationState {
|
||||
schema_version: SEPARATION_STATE_SCHEMA_VERSION.to_string(),
|
||||
trees,
|
||||
bound: Vec::new(),
|
||||
problematic_nodes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn terminal_ids(state: &SeparationState) -> HashSet<NodeId> {
|
||||
state.bound.iter().map(|n| n.node_id.clone())
|
||||
.chain(state.problematic_nodes.iter().map(|n| n.node_id.clone())).collect()
|
||||
state
|
||||
.bound
|
||||
.iter()
|
||||
.map(|n| n.node_id.clone())
|
||||
.chain(state.problematic_nodes.iter().map(|n| n.node_id.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn logical_leaves<'a>(node: &'a SeparationNode, extractable: bool, terminal: &HashSet<NodeId>, output: &mut Vec<&'a SeparationNode>) {
|
||||
fn logical_leaves<'a>(
|
||||
node: &'a SeparationNode,
|
||||
extractable: bool,
|
||||
terminal: &HashSet<NodeId>,
|
||||
output: &mut Vec<&'a SeparationNode>,
|
||||
) {
|
||||
let is_terminal = terminal.contains(&node.id);
|
||||
let children_terminal = node.children.iter().all(|child| terminal.contains(&child.id));
|
||||
let children_terminal = node
|
||||
.children
|
||||
.iter()
|
||||
.all(|child| terminal.contains(&child.id));
|
||||
if extractable && !is_terminal && children_terminal {
|
||||
output.push(node);
|
||||
return;
|
||||
@@ -100,34 +153,74 @@ fn overlaps(a: &SeparationNode, b: &SeparationNode) -> bool {
|
||||
let ay1 = a.global_pos_y_px as u64 + a.height_px as u64;
|
||||
let bx1 = b.global_pos_x_px as u64 + b.width_px as u64;
|
||||
let by1 = b.global_pos_y_px as u64 + b.height_px as u64;
|
||||
let width = ax1.min(bx1).saturating_sub(a.global_pos_x_px.max(b.global_pos_x_px) as u64);
|
||||
let height = ay1.min(by1).saturating_sub(a.global_pos_y_px.max(b.global_pos_y_px) as u64);
|
||||
let width = ax1
|
||||
.min(bx1)
|
||||
.saturating_sub(a.global_pos_x_px.max(b.global_pos_x_px) as u64);
|
||||
let height = ay1
|
||||
.min(by1)
|
||||
.saturating_sub(a.global_pos_y_px.max(b.global_pos_y_px) as u64);
|
||||
width > 0 && height > 0
|
||||
}
|
||||
|
||||
pub fn next_leaf_batch<'a>(state: &SeparationState, tree: &'a SeparationTree) -> Vec<&'a SeparationNode> {
|
||||
pub fn next_leaf_batch<'a>(
|
||||
state: &SeparationState,
|
||||
tree: &'a SeparationTree,
|
||||
) -> Vec<&'a SeparationNode> {
|
||||
let terminal = terminal_ids(state);
|
||||
let mut candidates = Vec::new();
|
||||
logical_leaves(&tree.root, tree.root_extractable, &terminal, &mut candidates);
|
||||
logical_leaves(
|
||||
&tree.root,
|
||||
tree.root_extractable,
|
||||
&terminal,
|
||||
&mut candidates,
|
||||
);
|
||||
let mut selected: Vec<&'a SeparationNode> = Vec::new();
|
||||
for candidate in candidates {
|
||||
if selected.iter().all(|other| !overlaps(candidate, other)) { selected.push(candidate); }
|
||||
if selected.iter().all(|other| !overlaps(candidate, other)) {
|
||||
selected.push(candidate);
|
||||
}
|
||||
}
|
||||
app_log!("ui_separation.batch_selected image_id={} leaf_nodes={}", tree.src_ui_design.as_str(), selected.len());
|
||||
app_log!(
|
||||
"ui_separation.batch_selected image_id={} leaf_nodes={}",
|
||||
tree.src_ui_design.as_str(),
|
||||
selected.len()
|
||||
);
|
||||
selected
|
||||
}
|
||||
|
||||
pub fn validate_binding_response(response: &BindingResp, batch: &[&SeparationNode]) -> Result<(), String> {
|
||||
let expected = batch.iter().map(|node| node.id.clone()).collect::<HashSet<_>>();
|
||||
pub fn validate_binding_response(
|
||||
response: &BindingResp,
|
||||
batch: &[&SeparationNode],
|
||||
) -> Result<(), String> {
|
||||
let expected = batch
|
||||
.iter()
|
||||
.map(|node| node.id.clone())
|
||||
.collect::<HashSet<_>>();
|
||||
let mut seen = HashSet::new();
|
||||
for decision in &response.decisions {
|
||||
let node_id = match decision { BindingDecision::Ok { to_node, .. } | BindingDecision::NeedRework { to_node, .. } => to_node };
|
||||
if !expected.contains(node_id) { return Err(format!("视觉绑定返回了未知节点:{}", node_id.as_str())); }
|
||||
if !seen.insert(node_id.clone()) { return Err(format!("视觉绑定重复返回节点:{}", node_id.as_str())); }
|
||||
if let BindingDecision::NeedRework { problem_description, .. } = decision {
|
||||
if problem_description.trim().is_empty() { return Err("NeedRework 必须包含问题描述".to_string()); }
|
||||
let node_id = match decision {
|
||||
BindingDecision::Ok { to_node, .. } | BindingDecision::NeedRework { to_node, .. } => {
|
||||
to_node
|
||||
}
|
||||
};
|
||||
if !expected.contains(node_id) {
|
||||
return Err(format!("视觉绑定返回了未知节点:{}", node_id.as_str()));
|
||||
}
|
||||
if !seen.insert(node_id.clone()) {
|
||||
return Err(format!("视觉绑定重复返回节点:{}", node_id.as_str()));
|
||||
}
|
||||
if let BindingDecision::NeedRework {
|
||||
problem_description,
|
||||
..
|
||||
} = decision
|
||||
{
|
||||
if problem_description.trim().is_empty() {
|
||||
return Err("NeedRework 必须包含问题描述".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
if seen.len() != expected.len() { return Err("视觉绑定未覆盖当前 batch 的全部节点".to_string()); }
|
||||
if seen.len() != expected.len() {
|
||||
return Err("视觉绑定未覆盖当前 batch 的全部节点".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -89,6 +89,27 @@ pub fn apply_batch_patch(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mark_batch_problematic(
|
||||
state: &mut SeparationState,
|
||||
tree_index: usize,
|
||||
batch: &[&SeparationNode],
|
||||
error: String,
|
||||
) {
|
||||
app_log!(
|
||||
"ui_separation.batch_problematic tree_index={} nodes={} error={}",
|
||||
tree_index,
|
||||
batch.len(),
|
||||
error
|
||||
);
|
||||
for node in batch {
|
||||
state.problematic_nodes.push(ProblematicNode {
|
||||
node_id: node.id.clone(),
|
||||
problem_description: error.clone(),
|
||||
rework_count: node.rework_count,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn increment_rework_count(node: &mut SeparationNode, id: &NodeId, count: u32) {
|
||||
if node.id == *id {
|
||||
node.rework_count = count;
|
||||
@@ -279,7 +300,6 @@ async fn visual_binding(
|
||||
let llm_config = llm_config.clone();
|
||||
async move {
|
||||
let request = LlmRunRequest::new(vec![
|
||||
LlmMessage::system("你是 UI 图片视觉绑定器。只根据图像判断区域,不做 OCR。"),
|
||||
LlmMessage::user_multimodal(vec![
|
||||
LlmMessageContentPart::InputText { text: prompt },
|
||||
LlmMessageContentPart::InputImage {
|
||||
@@ -456,8 +476,10 @@ pub(crate) async fn separate_ui_impl(
|
||||
tree_index,
|
||||
batch_index
|
||||
);
|
||||
mark_batch_problematic(&mut separation, tree_index, &batch, error);
|
||||
write_separation_state(&state_path, &separation)?;
|
||||
return Err(error);
|
||||
batch_index += 1;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let processed_url = match raw_image_edit(
|
||||
@@ -476,8 +498,10 @@ pub(crate) async fn separate_ui_impl(
|
||||
tree_index,
|
||||
batch_index
|
||||
);
|
||||
mark_batch_problematic(&mut separation, tree_index, &batch, error);
|
||||
write_separation_state(&state_path, &separation)?;
|
||||
return Err(error);
|
||||
batch_index += 1;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let processed_path = sidecar.join(format!("processed-{}.png", separation.bound.len()));
|
||||
@@ -489,8 +513,10 @@ pub(crate) async fn separate_ui_impl(
|
||||
tree_index,
|
||||
batch_index
|
||||
);
|
||||
mark_batch_problematic(&mut separation, tree_index, &batch, error);
|
||||
write_separation_state(&state_path, &separation)?;
|
||||
return Err(error);
|
||||
batch_index += 1;
|
||||
continue;
|
||||
}
|
||||
let binding = match visual_binding(source_url.clone(), processed_url, &batch).await {
|
||||
Ok(value) => value,
|
||||
@@ -500,8 +526,10 @@ pub(crate) async fn separate_ui_impl(
|
||||
tree_index,
|
||||
batch_index
|
||||
);
|
||||
mark_batch_problematic(&mut separation, tree_index, &batch, error);
|
||||
write_separation_state(&state_path, &separation)?;
|
||||
return Err(error);
|
||||
batch_index += 1;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
app_log!(
|
||||
@@ -550,8 +578,10 @@ pub(crate) async fn separate_ui_impl(
|
||||
tree_index,
|
||||
batch_index
|
||||
);
|
||||
mark_batch_problematic(&mut separation, tree_index, &batch, error);
|
||||
write_separation_state(&state_path, &separation)?;
|
||||
return Err(error);
|
||||
batch_index += 1;
|
||||
continue;
|
||||
}
|
||||
apply_batch_patch(&mut separation, tree_index, &binding.decisions, &cut_paths)?;
|
||||
write_separation_state(&state_path, &separation)?;
|
||||
|
||||
Reference in New Issue
Block a user