Skip to content

Commit 92cb6d5

Browse files
committed
feat(camera): desk view for Full Camera sections, with a covered tilt
A Full Camera section can now show a webcam tilted down onto the desk: "Desk view" in the inspector turns the camera 180°, switches the mirror off for that section (otherwise text on the page reads back to front) and shows the whole camera frame instead of the face crop. The timeline marks such sections with a rotate icon. The moment the camera is tilted is covered at both ends of the section: the whole camera picture is blurred and dimmed for the Full Camera grow (and the shrink), fading in and out around it, with a translated "Desk mode" label over it that can be switched off per section. Preview and export render it identically through the compositor's frame plan. - Rust: per-frame orientation (u/v bound swaps, crop bypass) and cover strength, a `LayerCB.cover` lane (HLSL, WGSL, MSL), the blur clamped to the camera's valid area so aligned decoder padding never bleeds in, two label text animations. - App: `rotation` / `mirror` / `deskLabel` on Full Camera regions (defaults are not stored), store and persistence, inspector controls, generated label annotations per projected piece, translations in every locale. - Tests for each layer; WGSL is validated with naga on every host.
1 parent b2e8635 commit 92cb6d5

45 files changed

Lines changed: 1662 additions & 74 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎crates/compositor/src/compositor_linux.rs‎

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,12 @@ fn layer_source(models: bool) -> String {
6767
/// en parcourant les 18 wallpapers livres) en laissant le jeu actif resident.
6868
const IMG_CACHE_BUDGET_BYTES: u64 = 512 * 1024 * 1024;
6969

70-
/// Taille du buffer uniforme d'un calque : `LayerCB` entier (176 octets), le `struct Layer` de
70+
/// Taille du buffer uniforme d'un calque : `LayerCB` entier (192 octets), le `struct Layer` de
7171
/// `layer.wgsl`. `blur.wgsl` n'en lit que les 128 premiers.
7272
const LAYER_BYTES: u64 = std::mem::size_of::<LayerCB>() as u64;
7373

7474
/// `&LayerCB` -> ses octets. `LayerCB` est `#[repr(C, align(16))]`, son layout EST le buffer
75-
/// uniforme WGSL (dix vec4 et un vec2 + 2 f32 = 176 octets).
75+
/// uniforme WGSL (douze vec4 = 192 octets).
7676
fn layer_bytes(cb: &LayerCB) -> &[u8] {
7777
unsafe { std::slice::from_raw_parts(cb as *const LayerCB as *const u8, LAYER_BYTES as usize) }
7878
}
@@ -2485,25 +2485,24 @@ impl Compositor {
24852485
let [cu0, cv0, cu1, cv1] = crate::frame_geometry::webcam_source_rect(
24862486
[wcw, wch],
24872487
[wtw as f32, wth as f32],
2488-
scene_ref
2489-
.as_ref()
2490-
.and_then(|scene| scene.layout.webcam_crop),
2488+
if g.webcam.full_frame {
2489+
None
2490+
} else {
2491+
scene_ref.as_ref().and_then(|scene| scene.layout.webcam_crop)
2492+
},
24912493
g.w_px[0] / g.w_px[1].max(0.0001),
24922494
);
2493-
// MIROIR : on inverse l'intervalle u. Le VS interpole `src`
2494-
// lineairement et `fs_main` ne re-clampe pas `i.uv`, donc un
2495-
// intervalle a l'envers suffit -- aucune retouche du WGSL. Apres le
2496-
// cover-crop les deux bornes sont strictement a l'interieur de la
2497-
// texture, donc le sampler ClampToEdge ne bave pas sur les bords.
2498-
let (u0, u1) = if lp.webcam_mirror { (cu1, cu0) } else { (cu0, cu1) };
2495+
// Mirror and the desk-shot turn are both bound swaps: u for horizontal, v for vertical.
2496+
let (u0, u1) = if g.webcam.flip_u { (cu1, cu0) } else { (cu0, cu1) };
2497+
let (v0, v1) = if g.webcam.flip_v { (cv1, cv0) } else { (cv0, cv1) };
24992498
// `src_prev` doit valoir EXACTEMENT le `src` de ce draw, miroir
25002499
// compris : le shader s'en sert pour reconstruire l'UV de la frame
25012500
// precedente, et un rect source qui ne correspond pas au calque
25022501
// dessine ferait diverger la trainee vers une zone de la texture qui
25032502
// n'a jamais ete affichee. Seul `dst_prev` porte le mouvement.
25042503
let cb = LayerCB {
25052504
dst: g.w_dst,
2506-
src: [u0, cv0, u1, cv1],
2505+
src: [u0, v0, u1, v1],
25072506
quad_px: g.w_px,
25082507
radius_px: g.w_radius,
25092508
mode: 0.0,
@@ -2515,9 +2514,10 @@ impl Compositor {
25152514
// `fx.z` = mode, `fx.w` = intensite du flou. Contrat commun aux
25162515
// trois back-ends, cf. `layer.wgsl` et `webcam-segmentation.md`.
25172516
fx: [w_valid[0], w_valid[1], effect_code, blur_intensity],
2518-
src_prev: [u0, cv0, u1, cv1],
2517+
src_prev: [u0, v0, u1, v1],
25192518
dst_prev: g.w_dst_prev,
25202519
mb: [g.mb_taps, g.mb_amount, 1.0, 0.0],
2520+
cover: [g.webcam_cover, 0.04 * g.w_px[0].min(g.w_px[1]) * g.webcam_cover, 0.35, 0.0],
25212521
..Default::default()
25222522
};
25232523
// Le masque est lie par `make_bind` sur tous les draws, pas seulement
@@ -4888,6 +4888,9 @@ mod tests {
48884888
clip_index: None,
48894889
start_sec: 2.0,
48904890
end_sec: 8.0,
4891+
rotation: 0,
4892+
mirror: None,
4893+
full_frame: false,
48914894
});
48924895
// 5 s : la montee (~1 s) est finie, le retour (~1,5 s) n'a pas commence.
48934896
comp.set_timeline_time(Some(5.0));

‎crates/compositor/src/compositor_macos.rs‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2663,10 +2663,16 @@ impl Compositor {
26632663
let [cu0, cv0, cu1, cv1] = crate::frame_geometry::webcam_source_rect(
26642664
[wcw, wch],
26652665
[wtw as f32, wth as f32],
2666-
scene_ref.as_ref().and_then(|scene| scene.layout.webcam_crop),
2666+
if g.webcam.full_frame {
2667+
None
2668+
} else {
2669+
scene_ref.as_ref().and_then(|scene| scene.layout.webcam_crop)
2670+
},
26672671
g.w_px[0] / g.w_px[1].max(0.0001),
26682672
);
2669-
let (u0, u1) = if lp.webcam_mirror { (cu1, cu0) } else { (cu0, cu1) };
2673+
// Mirror and the desk-shot turn are both bound swaps: u for horizontal, v for vertical.
2674+
let (u0, u1) = if g.webcam.flip_u { (cu1, cu0) } else { (cu0, cu1) };
2675+
let (v0, v1) = if g.webcam.flip_v { (cv1, cv0) } else { (cv0, cv1) };
26702676
let webcam_is_block = matches!(
26712677
g.scene_preset.as_deref(),
26722678
Some("dual-frame") | Some("vertical-stack")
@@ -2733,17 +2739,18 @@ impl Compositor {
27332739
enc,
27342740
&LayerCB {
27352741
dst: g.w_dst,
2736-
src: [u0, cv0, u1, cv1],
2742+
src: [u0, v0, u1, v1],
27372743
quad_px: g.w_px,
27382744
radius_px: g.w_radius,
27392745
mode: 0.0,
27402746
// `color.a` porte l'alpha du découpage (`color.a * personne`) ; le RGB n'est
27412747
// plus lu, le fond ayant déjà été peint sous la caméra.
27422748
color: [0.0, 0.0, 0.0, 1.0],
27432749
fx: [w_valid[0], w_valid[1], effect_code, blur_intensity],
2744-
src_prev: [u0, cv0, u1, cv1],
2750+
src_prev: [u0, v0, u1, v1],
27452751
dst_prev: g.w_dst_prev,
27462752
mb: [g.mb_taps, g.mb_amount, 1.0, 0.0],
2753+
cover: [g.webcam_cover, 0.04 * g.w_px[0].min(g.w_px[1]) * g.webcam_cover, 0.35, 0.0],
27472754
..Default::default()
27482755
},
27492756
wy,

‎crates/compositor/src/compositor_windows.rs‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2438,13 +2438,16 @@ impl Compositor {
24382438
let [su0, sv0, su1, sv1] = crate::frame_geometry::webcam_source_rect(
24392439
[wcw, wch],
24402440
[wtw as f32, wth as f32],
2441-
scene_ref
2442-
.as_ref()
2443-
.and_then(|scene| scene.layout.webcam_crop),
2441+
if g.webcam.full_frame {
2442+
None
2443+
} else {
2444+
scene_ref.as_ref().and_then(|scene| scene.layout.webcam_crop)
2445+
},
24442446
w_px[0] / w_px[1].max(0.0001),
24452447
);
2446-
// miroir = échanger les bornes u du rect source (flip horizontal).
2447-
let (u0, u1) = if lp.webcam_mirror { (su1, su0) } else { (su0, su1) };
2448+
// Mirror and the desk-shot turn are both bound swaps: u for horizontal, v for vertical.
2449+
let (u0, u1) = if g.webcam.flip_u { (su1, su0) } else { (su0, su1) };
2450+
let (v0, v1) = if g.webcam.flip_v { (sv1, sv0) } else { (sv0, sv1) };
24482451
if lp.has_webcam {
24492452
// L'ombre portée appartient à la bulle flottante PiP : elle se retire avec elle
24502453
// (`shape_fade`), pour qu'au plein écran plus rien n'encadre la caméra. C'est une
@@ -2508,17 +2511,18 @@ impl Compositor {
25082511
self.draw_video(
25092512
&LayerCB {
25102513
dst: w_dst,
2511-
src: [u0, sv0, u1, sv1],
2514+
src: [u0, v0, u1, v1],
25122515
quad_px: w_px,
25132516
radius_px: w_radius,
25142517
mode: 0.0,
25152518
// `color.a` porte l'alpha du découpage (`color.a * personne`) ; le RGB n'est
25162519
// plus lu, le fond ayant déjà été peint sous la caméra.
25172520
color: [0.0, 0.0, 0.0, 1.0],
25182521
fx: [w_valid[0], w_valid[1], effect_code, blur_intensity],
2519-
src_prev: [u0, sv0, u1, sv1], // src fixe (pas de zoom webcam)
2522+
src_prev: [u0, v0, u1, v1], // src fixe (pas de zoom webcam)
25202523
dst_prev: w_dst_prev,
25212524
mb: [mb_taps, mb_amount, 1.0, 0.0],
2525+
cover: [g.webcam_cover, 0.04 * w_px[0].min(w_px[1]) * g.webcam_cover, 0.35, 0.0],
25222526
..Default::default()
25232527
},
25242528
&wy,

0 commit comments

Comments
 (0)