修复图片改造后二维清晰度降级问题
前端按图层真实尺寸恢复图片改造比例与清晰度 后端在角色去背景后恢复生成原图像素尺寸 补充前后端二千像素尺寸回归测试 同步更新图片画布技术文档与项目决策记录
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user