修复图片改造后二维清晰度降级问题

前端按图层真实尺寸恢复图片改造比例与清晰度

后端在角色去背景后恢复生成原图像素尺寸

补充前后端二千像素尺寸回归测试

同步更新图片画布技术文档与项目决策记录
This commit is contained in:
MengHao
2026-07-17 19:43:49 +08:00
parent 387a1c26e3
commit da3aac8257
5 changed files with 177 additions and 22 deletions
@@ -1852,11 +1852,28 @@ pub(crate) async fn generate_editor_image_for_owner(
));
}
};
image = removal.image;
let removal_provider = removal.provider;
let (restored_removal_image, postprocess_dimension_error) =
restore_editor_postprocessed_image_output_dimensions_or_original(
removal.image,
provider_width,
provider_height,
);
if let Some(error) = postprocess_dimension_error {
tracing::warn!(
task_id = %generated.task_id,
provider = removal_provider,
provider_width,
provider_height,
error = ?error,
"角色透明图尺寸恢复失败,保留去背景服务原始输出"
);
}
image = restored_removal_image;
output_prompt = "去除纯色背景".to_string();
output_actual_prompt = None;
output_model = removal.model;
output_provider = removal.provider.to_string();
output_provider = removal_provider.to_string();
output_generation_inputs =
build_editor_derived_asset_generation_inputs("角色抠图", &output_model, &source_record);
Some(source_record)
@@ -2418,9 +2435,10 @@ async fn ensure_editor_image_edit_source_allowed(
ensure_editor_image_edit_asset_kind_allowed(asset_kind)
}
fn encode_editor_image_edit_png(
fn encode_editor_image_png(
image: image::DynamicImage,
failure_status: StatusCode,
provider: &str,
failure_message: &str,
) -> Result<Vec<u8>, AppError> {
let mut bytes = Cursor::new(Vec::new());
@@ -2428,13 +2446,21 @@ fn encode_editor_image_edit_png(
.write_to(&mut bytes, image::ImageFormat::Png)
.map_err(|error| {
AppError::from_status(failure_status).with_details(json!({
"provider": "editor-image-edit",
"provider": provider,
"message": format!("{failure_message}:{error}"),
}))
})?;
Ok(bytes.into_inner())
}
fn encode_editor_image_edit_png(
image: image::DynamicImage,
failure_status: StatusCode,
failure_message: &str,
) -> Result<Vec<u8>, AppError> {
encode_editor_image_png(image, failure_status, "editor-image-edit", failure_message)
}
fn prepare_editor_image_edit_references(
reference_images: &mut [OpenAiReferenceImage],
target_size: &str,
@@ -2571,6 +2597,51 @@ fn restore_editor_generated_image_output_dimensions_or_original(
}
}
fn restore_editor_postprocessed_image_output_dimensions(
output: &DownloadedOpenAiImage,
target_width: u32,
target_height: u32,
) -> Result<Option<DownloadedOpenAiImage>, AppError> {
let decoded = image::load_from_memory(output.bytes.as_slice()).map_err(|error| {
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
"provider": "editor-character-image",
"message": format!("角色透明图不是有效图片:{error}"),
}))
})?;
if decoded.width() == target_width && decoded.height() == target_height {
return Ok(None);
}
let restored = decoded.resize_exact(
target_width,
target_height,
image::imageops::FilterType::Lanczos3,
);
Ok(Some(DownloadedOpenAiImage {
bytes: encode_editor_image_png(
restored,
StatusCode::BAD_GATEWAY,
"editor-character-image",
"恢复角色透明图尺寸失败",
)?,
mime_type: "image/png".to_string(),
extension: "png".to_string(),
}))
}
fn restore_editor_postprocessed_image_output_dimensions_or_original(
output: DownloadedOpenAiImage,
target_width: u32,
target_height: u32,
) -> (DownloadedOpenAiImage, Option<AppError>) {
match restore_editor_postprocessed_image_output_dimensions(&output, target_width, target_height)
{
Ok(Some(restored)) => (restored, None),
Ok(None) => (output, None),
Err(error) => (output, Some(error)),
}
}
fn restore_editor_generated_image_output_dimensions(
output: &DownloadedOpenAiImage,
model: &str,
@@ -8469,6 +8540,33 @@ mod tests {
assert_eq!(restored.extension, "png");
}
#[test]
fn character_postprocess_restores_provider_dimensions() {
let image = image::DynamicImage::new_rgba8(1024, 1024);
let mut bytes = Cursor::new(Vec::new());
image
.write_to(&mut bytes, image::ImageFormat::Png)
.expect("test image should encode");
let postprocessed = DownloadedOpenAiImage {
bytes: bytes.into_inner(),
mime_type: "image/png".to_string(),
extension: "png".to_string(),
};
let restored =
restore_editor_postprocessed_image_output_dimensions(&postprocessed, 2048, 2048)
.expect("postprocessed image should restore provider dimensions")
.expect("1K postprocessed image should be resized back to 2K");
let restored_image = image::load_from_memory(restored.bytes.as_slice()).unwrap();
assert_eq!(
(restored_image.width(), restored_image.height()),
(2048, 2048)
);
assert_eq!(restored.mime_type, "image/png");
assert_eq!(restored.extension, "png");
}
#[test]
fn publication_material_generation_restores_provider_output_to_workflow_dimensions() {
let image = image::DynamicImage::new_rgba8(944, 704);