Skip to content

Commit 346561b

Browse files
Merge pull request #2611 from yashranaway/fix/rendering-background-resize
fix(rendering): recrop image backgrounds when output size changes
2 parents 9b93f00 + e823702 commit 346561b

2 files changed

Lines changed: 138 additions & 2 deletions

File tree

‎crates/rendering/src/layers/background.rs‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ pub enum Inner {
337337
AnimatedGradient(Box<AnimatedGradientLayer>),
338338
Image {
339339
path: String,
340+
output_size: (u32, u32),
340341
bind_group: wgpu::BindGroup,
341342
},
342343
ColorOrGradient {
@@ -390,8 +391,10 @@ impl BackgroundLayer {
390391
Background::Image { path } => {
391392
match &self.inner {
392393
Some(Inner::Image {
393-
path: current_path, ..
394-
}) if current_path == &path => {}
394+
path: current_path,
395+
output_size,
396+
..
397+
}) if current_path == &path && *output_size == uniforms.output_size => {}
395398
_ => {
396399
let texture = match constants
397400
.background_textures
@@ -456,6 +459,7 @@ impl BackgroundLayer {
456459
self.generation += 1;
457460
self.inner = Some(Inner::Image {
458461
path,
462+
output_size: uniforms.output_size,
459463
bind_group: self.image_pipeline.bind_group(
460464
device,
461465
&uniform_buffer,
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
use cap_project::{
2+
AspectRatio, BackgroundSource, CursorEvents, ProjectConfiguration, RecordingMeta, XY,
3+
};
4+
use cap_rendering::{
5+
DecodedSegmentFrames, FrameRenderer, ProjectUniforms, RenderOptions, RenderVideoConstants,
6+
RendererLayers, ZoomTransformTimeline,
7+
};
8+
9+
#[tokio::test]
10+
async fn image_background_resize_matches_fresh_layers() {
11+
cap_rendering::enable_blur_result_cache();
12+
let recording_meta: RecordingMeta = serde_json::from_value(serde_json::json!({
13+
"pretty_name": "background-resize",
14+
"display": { "path": "display.mp4", "fps": 30 },
15+
"camera": null,
16+
"audio": null,
17+
"cursor": null
18+
}))
19+
.unwrap();
20+
let meta = recording_meta.studio_meta().unwrap().clone();
21+
let screen_size = XY::new(128, 72);
22+
let constants = RenderVideoConstants::new_with_options(
23+
RenderOptions {
24+
screen_size,
25+
camera_size: None,
26+
preserve_screen_alpha: false,
27+
},
28+
recording_meta,
29+
meta,
30+
)
31+
.await
32+
.unwrap();
33+
let directory = tempfile::tempdir().unwrap();
34+
let cursor = CursorEvents::default();
35+
let frames = DecodedSegmentFrames {
36+
screen_size,
37+
screen_frame: None,
38+
camera_frame: None,
39+
segment_time: 0.0,
40+
recording_time: 0.0,
41+
segment_has_camera: false,
42+
};
43+
44+
for (width, height) in [(96, 64), (64, 96)] {
45+
let path = directory.path().join(format!("{width}x{height}.png"));
46+
image::RgbaImage::from_fn(width, height, |x, y| {
47+
image::Rgba([(x * 2) as u8, (y * 2) as u8, 64, 255])
48+
})
49+
.save(&path)
50+
.unwrap();
51+
for blur in [0.0, 25.0] {
52+
let mut project = ProjectConfiguration::default();
53+
project.background.source = BackgroundSource::Image {
54+
path: Some(path.to_string_lossy().into_owned()),
55+
};
56+
project.background.blur = blur;
57+
project.cursor.hide = true;
58+
let mut renderer = FrameRenderer::new(&constants);
59+
let mut layers = RendererLayers::new_for_preparing_preview(
60+
&constants.device,
61+
&constants.queue,
62+
constants.is_software_adapter,
63+
&project,
64+
)
65+
.unwrap();
66+
67+
for aspect in [
68+
AspectRatio::Wide,
69+
AspectRatio::Wide,
70+
AspectRatio::Square,
71+
AspectRatio::Vertical,
72+
AspectRatio::Wide,
73+
] {
74+
project.aspect_ratio = Some(aspect);
75+
let zoom = ZoomTransformTimeline::from_project(&project, &cursor, 1.0, screen_size);
76+
let uniforms = ProjectUniforms::new(
77+
&constants,
78+
&project,
79+
0,
80+
30,
81+
XY::new(128, 128),
82+
&cursor,
83+
&frames,
84+
1.0,
85+
&zoom,
86+
);
87+
let actual = renderer
88+
.render_immediate(
89+
frames.clone(),
90+
uniforms.clone(),
91+
&cursor,
92+
false,
93+
&mut layers,
94+
)
95+
.await
96+
.unwrap();
97+
let mut fresh_layers = RendererLayers::new_for_preparing_preview(
98+
&constants.device,
99+
&constants.queue,
100+
constants.is_software_adapter,
101+
&project,
102+
)
103+
.unwrap();
104+
let expected = renderer
105+
.render_immediate(frames.clone(), uniforms, &cursor, false, &mut fresh_layers)
106+
.await
107+
.unwrap();
108+
109+
let center = (expected.height / 2 * expected.padded_bytes_per_row
110+
+ expected.width / 2 * 4) as usize;
111+
assert!(expected.data[center] > expected.data[0]);
112+
assert!(expected.data[center + 1] > expected.data[1]);
113+
114+
for y in 0..actual.height as usize {
115+
let start = y * actual.padded_bytes_per_row as usize;
116+
let end = start + actual.width as usize * 4;
117+
let error = actual.data[start..end]
118+
.iter()
119+
.zip(&expected.data[start..end])
120+
.map(|(a, b)| a.abs_diff(*b))
121+
.max()
122+
.unwrap();
123+
assert_eq!(
124+
error, 0,
125+
"{width}x{height} image, {:?}, blur {blur}, row {y}",
126+
project.aspect_ratio,
127+
);
128+
}
129+
}
130+
}
131+
}
132+
}

0 commit comments

Comments
 (0)