将自动分离图像处理移出异步执行器
在 spawn_blocking 中构建标记图并编码写入处理图 在 spawn_blocking 中完成处理图裁切与文件输出 保留 image-edit 和视觉绑定网络请求的 async 调度边界
This commit is contained in:
+80
-24
@@ -16,7 +16,7 @@ use platform_llm::{
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
pub fn apply_batch_patch(
|
||||
state: &mut SeparationState,
|
||||
tree_index: usize,
|
||||
@@ -167,9 +167,32 @@ async fn raw_image_edit(
|
||||
.ok_or_else(|| "图片分离响应没有图像".to_string())
|
||||
}
|
||||
|
||||
fn build_marked_image(
|
||||
async fn build_marked_image(
|
||||
source_url: String,
|
||||
nodes: Vec<SeparationNode>,
|
||||
target: PathBuf,
|
||||
) -> Result<String, String> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let areas = nodes
|
||||
.iter()
|
||||
.map(|node| {
|
||||
(
|
||||
node.global_pos_x_px,
|
||||
node.global_pos_y_px,
|
||||
node.width_px,
|
||||
node.height_px,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
build_marked_image_blocking(&source_url, &areas, &target)
|
||||
})
|
||||
.await
|
||||
.map_err(|error| format!("构建标记图任务失败:{error}"))?
|
||||
}
|
||||
|
||||
fn build_marked_image_blocking(
|
||||
source_url: &str,
|
||||
nodes: &[&SeparationNode],
|
||||
nodes: &[(u32, u32, u32, u32)],
|
||||
target: &Path,
|
||||
) -> Result<String, String> {
|
||||
let encoded = source_url
|
||||
@@ -184,17 +207,15 @@ fn build_marked_image(
|
||||
.to_rgba8();
|
||||
let width = image.width();
|
||||
let height = image.height();
|
||||
for node in nodes {
|
||||
let x0 = node.global_pos_x_px.min(width.saturating_sub(1));
|
||||
let y0 = node.global_pos_y_px.min(height.saturating_sub(1));
|
||||
let x1 = node
|
||||
.global_pos_x_px
|
||||
.saturating_add(node.width_px)
|
||||
for &(global_pos_x_px, global_pos_y_px, node_width_px, node_height_px) in nodes {
|
||||
let x0 = global_pos_x_px.min(width.saturating_sub(1));
|
||||
let y0 = global_pos_y_px.min(height.saturating_sub(1));
|
||||
let x1 = global_pos_x_px
|
||||
.saturating_add(node_width_px)
|
||||
.min(width)
|
||||
.saturating_sub(1);
|
||||
let y1 = node
|
||||
.global_pos_y_px
|
||||
.saturating_add(node.height_px)
|
||||
let y1 = global_pos_y_px
|
||||
.saturating_add(node_height_px)
|
||||
.min(height)
|
||||
.saturating_sub(1);
|
||||
if x0 >= x1 || y0 >= y1 {
|
||||
@@ -229,6 +250,23 @@ fn build_marked_image(
|
||||
))
|
||||
}
|
||||
|
||||
async fn write_processed_image(processed_url: String, target: PathBuf) -> Result<(), String> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let processed_bytes = base64::engine::general_purpose::STANDARD
|
||||
.decode(
|
||||
processed_url
|
||||
.split_once(',')
|
||||
.map(|(_, data)| data)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.map_err(|error| format!("解析处理图失败:{error}"))?;
|
||||
fs::write(&target, processed_bytes)
|
||||
.map_err(|error| format!("写入处理图失败:{}: {error}", target.display()))
|
||||
})
|
||||
.await
|
||||
.map_err(|error| format!("写入处理图任务失败:{error}"))?
|
||||
}
|
||||
|
||||
async fn visual_binding(
|
||||
source_url: String,
|
||||
processed_url: String,
|
||||
@@ -337,7 +375,13 @@ pub(crate) async fn separate_ui_impl(
|
||||
let prompt =
|
||||
gen_extract_prompt(batch.iter().map(|n| n.note.clone()).collect::<Vec<_>>());
|
||||
let marker_path = sidecar.join(format!("marked-{}.png", separation.bound.len()));
|
||||
let marked_url = match build_marked_image(&source_url, &batch, &marker_path) {
|
||||
let marked_url = match build_marked_image(
|
||||
source_url.clone(),
|
||||
batch_nodes.clone(),
|
||||
marker_path,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
mark_batch_problematic(&mut separation, tree_index, &batch, error);
|
||||
@@ -362,15 +406,7 @@ pub(crate) async fn separate_ui_impl(
|
||||
}
|
||||
};
|
||||
let processed_path = sidecar.join(format!("processed-{}.png", separation.bound.len()));
|
||||
let processed_bytes = base64::engine::general_purpose::STANDARD
|
||||
.decode(
|
||||
processed_url
|
||||
.split_once(',')
|
||||
.map(|(_, d)| d)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
fs::write(&processed_path, processed_bytes).map_err(|e| e.to_string())?;
|
||||
write_processed_image(processed_url.clone(), processed_path.clone()).await?;
|
||||
let binding = match visual_binding(source_url.clone(), processed_url, &batch).await {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
@@ -388,7 +424,13 @@ pub(crate) async fn separate_ui_impl(
|
||||
} = decision
|
||||
{
|
||||
let cut_path = sidecar.join(format!("cut-{}.png", to_node.as_str()));
|
||||
match cut_processed_image(&processed_path, separated_image_area, &cut_path) {
|
||||
match cut_processed_image(
|
||||
processed_path.clone(),
|
||||
*separated_image_area,
|
||||
cut_path.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
cut_paths
|
||||
.insert(to_node.clone(), cut_path.to_string_lossy().to_string());
|
||||
@@ -414,7 +456,21 @@ pub(crate) async fn separate_ui_impl(
|
||||
Ok(separation_dto(&separation))
|
||||
}
|
||||
|
||||
fn cut_processed_image(source: &Path, area: &BindingArea, target: &Path) -> Result<(), String> {
|
||||
async fn cut_processed_image(
|
||||
source: PathBuf,
|
||||
area: BindingArea,
|
||||
target: PathBuf,
|
||||
) -> Result<(), String> {
|
||||
tokio::task::spawn_blocking(move || cut_processed_image_blocking(&source, &area, &target))
|
||||
.await
|
||||
.map_err(|error| format!("裁切处理图任务失败:{error}"))?
|
||||
}
|
||||
|
||||
fn cut_processed_image_blocking(
|
||||
source: &Path,
|
||||
area: &BindingArea,
|
||||
target: &Path,
|
||||
) -> Result<(), String> {
|
||||
let image = image::open(source).map_err(|e| format!("读取处理图失败:{e}"))?;
|
||||
area.validate_in(image.width(), image.height())?;
|
||||
let cropped = image.crop_imm(
|
||||
|
||||
Reference in New Issue
Block a user