From 75184d07714be40a470eb5257b84c64ce6247977 Mon Sep 17 00:00:00 2001 From: Linghong Date: Mon, 20 Jul 2026 13:37:55 +0000 Subject: [PATCH] =?UTF-8?q?=E5=9B=BA=E5=AE=9A=E6=8A=BD=E5=B8=A7=E5=83=8F?= =?UTF-8?q?=E7=B4=A0=E6=A0=BC=E5=BC=8F=E5=B9=B6=E6=94=B6=E6=95=9B=E5=8A=A8?= =?UTF-8?q?=E4=BD=9C=E5=B8=A7=E5=9B=BE=E5=83=8F=E5=A4=84=E7=90=86=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ffmpeg 抽帧显式指定 -pix_fmt rgb24,消除 to_rgb8 截断 alpha 的隐患 抽出 compute_contain_dimensions 与 image_format_from_mime 供两条帧路径共用 尺寸已匹配时跳过无效的 Triangle 重采样 Co-Authored-By: Claude Fable 5 --- .../src/character_animation_assets.rs | 79 +++++++++++-------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/server-rs/crates/api-server/src/character_animation_assets.rs b/server-rs/crates/api-server/src/character_animation_assets.rs index 0400a0ef8..370d82ca8 100644 --- a/server-rs/crates/api-server/src/character_animation_assets.rs +++ b/server-rs/crates/api-server/src/character_animation_assets.rs @@ -4287,6 +4287,8 @@ fn extract_video_frame_to_png( input_path.to_string_lossy().as_ref(), "-frames:v", "1", + "-pix_fmt", + "rgb24", "-f", "image2", output_path.to_string_lossy().as_ref(), @@ -4386,13 +4388,7 @@ fn prepare_editor_character_animation_bgfilter_input( target_width: u32, target_height: u32, ) -> Result { - let image_format = match mime_type { - "image/png" => Some(ImageFormat::Png), - "image/jpeg" | "image/jpg" => Some(ImageFormat::Jpeg), - "image/webp" => Some(ImageFormat::WebP), - _ => None, - }; - let image = match image_format { + let image = match image_format_from_mime(mime_type) { Some(format) => image::load_from_memory_with_format(source, format), None => image::load_from_memory(source), } @@ -4404,19 +4400,13 @@ fn prepare_editor_character_animation_bgfilter_input( })? .to_rgb8(); - let target_width = target_width.max(1); - let target_height = target_height.max(1); - let source_width = image.width().max(1); - let source_height = image.height().max(1); - let scale = (target_width as f32 / source_width as f32) - .min(target_height as f32 / source_height as f32); - let draw_width = ((source_width as f32 * scale).round() as u32) - .max(1) - .min(target_width); - let draw_height = ((source_height as f32 * scale).round() as u32) - .max(1) - .min(target_height); - let resized = image::imageops::resize(&image, draw_width, draw_height, FilterType::Triangle); + let (draw_width, draw_height) = + compute_contain_dimensions(image.width(), image.height(), target_width, target_height); + let resized = if (draw_width, draw_height) == (image.width(), image.height()) { + image + } else { + image::imageops::resize(&image, draw_width, draw_height, FilterType::Triangle) + }; let mut encoded = Vec::new(); let encoder = PngEncoder::new(&mut encoded); @@ -4448,13 +4438,7 @@ fn finalize_animation_frame_payload( frame_height: u32, apply_chroma_key: bool, ) -> Result { - let image_format = match mime_type { - "image/png" => Some(ImageFormat::Png), - "image/jpeg" | "image/jpg" => Some(ImageFormat::Jpeg), - "image/webp" => Some(ImageFormat::WebP), - _ => None, - }; - let mut image = match image_format { + let mut image = match image_format_from_mime(mime_type) { Some(format) => image::load_from_memory_with_format(source, format), None => image::load_from_memory(source), } @@ -4501,8 +4485,39 @@ fn finalize_animation_frame_payload( fn contain_rgba_image(source: &RgbaImage, target_width: u32, target_height: u32) -> RgbaImage { let mut canvas = RgbaImage::from_pixel(target_width, target_height, Rgba([0, 0, 0, 0])); - let source_width = source.width().max(1); - let source_height = source.height().max(1); + let (draw_width, draw_height) = + compute_contain_dimensions(source.width(), source.height(), target_width, target_height); + let offset_x = ((target_width - draw_width) / 2) as i64; + let offset_y = ((target_height - draw_height) / 2) as i64; + if (draw_width, draw_height) == (source.width(), source.height()) { + image::imageops::overlay(&mut canvas, source, offset_x, offset_y); + } else { + let resized = + image::imageops::resize(source, draw_width, draw_height, FilterType::Triangle); + image::imageops::overlay(&mut canvas, &resized, offset_x, offset_y); + } + canvas +} + +fn image_format_from_mime(mime_type: &str) -> Option { + match mime_type { + "image/png" => Some(ImageFormat::Png), + "image/jpeg" | "image/jpg" => Some(ImageFormat::Jpeg), + "image/webp" => Some(ImageFormat::WebP), + _ => None, + } +} + +fn compute_contain_dimensions( + source_width: u32, + source_height: u32, + target_width: u32, + target_height: u32, +) -> (u32, u32) { + let target_width = target_width.max(1); + let target_height = target_height.max(1); + let source_width = source_width.max(1); + let source_height = source_height.max(1); let scale = (target_width as f32 / source_width as f32) .min(target_height as f32 / source_height as f32); let draw_width = ((source_width as f32 * scale).round() as u32) @@ -4511,11 +4526,7 @@ fn contain_rgba_image(source: &RgbaImage, target_width: u32, target_height: u32) let draw_height = ((source_height as f32 * scale).round() as u32) .max(1) .min(target_height); - let resized = image::imageops::resize(source, draw_width, draw_height, FilterType::Triangle); - let offset_x = ((target_width - draw_width) / 2) as i64; - let offset_y = ((target_height - draw_height) / 2) as i64; - image::imageops::overlay(&mut canvas, &resized, offset_x, offset_y); - canvas + (draw_width, draw_height) } async fn load_media_source_payload(