还原共享抽帧像素格式并在 BgFilter 消费点合成 alpha
撤掉 extract_video_frames_to_png 里无条件的 -pix_fmt rgb24,共享抽帧不再携带 BgFilter 专属策略 prepare_editor_character_animation_bgfilter_input 解码后先按白底合成 alpha 再转 RGB,补上对应回归测试 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4334,8 +4334,6 @@ fn extract_video_frames_to_png(
|
||||
format!("[f{frame_index}]"),
|
||||
"-frames:v".to_string(),
|
||||
"1".to_string(),
|
||||
"-pix_fmt".to_string(),
|
||||
"rgb24".to_string(),
|
||||
"-f".to_string(),
|
||||
"image2".to_string(),
|
||||
output_path.to_string_lossy().into_owned(),
|
||||
@@ -4471,6 +4469,25 @@ fn run_process_with_timeout(
|
||||
}
|
||||
}
|
||||
|
||||
/// BgFilter 只接受不透明 RGB 输入;直接丢弃 alpha 会让全透明像素下未定义的
|
||||
/// RGB 值以杂色进入抠图,这里先按白底合成再转 RGB。
|
||||
fn flatten_alpha_onto_white_rgb(image: image::DynamicImage) -> image::RgbImage {
|
||||
if !image.color().has_alpha() {
|
||||
return image.to_rgb8();
|
||||
}
|
||||
let rgba = image.to_rgba8();
|
||||
let mut flattened = image::RgbImage::new(rgba.width(), rgba.height());
|
||||
for (source, target) in rgba.pixels().zip(flattened.pixels_mut()) {
|
||||
let [red, green, blue, alpha] = source.0;
|
||||
let opacity = f32::from(alpha) / 255.0;
|
||||
let blend = |channel: u8| -> u8 {
|
||||
(f32::from(channel) * opacity + 255.0 * (1.0 - opacity)).round() as u8
|
||||
};
|
||||
target.0 = [blend(red), blend(green), blend(blue)];
|
||||
}
|
||||
flattened
|
||||
}
|
||||
|
||||
fn prepare_editor_character_animation_bgfilter_input(
|
||||
source: &[u8],
|
||||
mime_type: &str,
|
||||
@@ -4486,8 +4503,8 @@ fn prepare_editor_character_animation_bgfilter_input(
|
||||
"provider": "character-animation",
|
||||
"message": format!("解析 BgFilter 输入动作帧图片失败:{error}"),
|
||||
}))
|
||||
})?
|
||||
.to_rgb8();
|
||||
})?;
|
||||
let image = flatten_alpha_onto_white_rgb(image);
|
||||
|
||||
let (draw_width, draw_height) =
|
||||
compute_contain_dimensions(image.width(), image.height(), target_width, target_height);
|
||||
@@ -6478,6 +6495,38 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_character_animation_bgfilter_input_flattens_alpha_onto_white() {
|
||||
let mut source = RgbaImage::from_pixel(8, 8, Rgba([17, 99, 201, 255]));
|
||||
source.put_pixel(0, 0, Rgba([255, 0, 0, 0]));
|
||||
source.put_pixel(1, 0, Rgba([0, 0, 0, 127]));
|
||||
let mut source_png = Vec::new();
|
||||
PngEncoder::new(&mut source_png)
|
||||
.write_image(
|
||||
source.as_raw(),
|
||||
source.width(),
|
||||
source.height(),
|
||||
ColorType::Rgba8.into(),
|
||||
)
|
||||
.expect("source RGBA frame should encode");
|
||||
|
||||
let prepared = prepare_editor_character_animation_bgfilter_input(
|
||||
source_png.as_slice(),
|
||||
"image/png",
|
||||
8,
|
||||
8,
|
||||
)
|
||||
.expect("BgFilter input should be prepared");
|
||||
let decoded =
|
||||
image::load_from_memory_with_format(prepared.bytes.as_slice(), ImageFormat::Png)
|
||||
.expect("prepared BgFilter input should decode")
|
||||
.to_rgb8();
|
||||
|
||||
assert_eq!(decoded.get_pixel(0, 0).0, [255, 255, 255]);
|
||||
assert_eq!(decoded.get_pixel(1, 0).0, [128, 128, 128]);
|
||||
assert_eq!(decoded.get_pixel(2, 0).0, [17, 99, 201]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_character_animation_final_frame_adds_transparent_vertical_padding() {
|
||||
let source = RgbaImage::from_pixel(323, 434, Rgba([31, 127, 223, 191]));
|
||||
|
||||
Reference in New Issue
Block a user