Skip to content

Commit edf03c7

Browse files
committed
feat(background): animate image wallpapers with the same three motions
Drift is a slow zoom and pan, safe on a photo since it deforms nothing. Aurora lets the image flow under a slow noise and passes the gradient aurora's light over it. Waves ripple the image along the 135° diagonal of the offered gradients and light their crests. Every motion zooms the image by at least 6 % and never moves the read further than the margin that zoom leaves, so nothing outside the image is ever read. The motion control now shows for an image too. A solid colour still hides it, and so does any string the compositor cannot load as an image, such as a radial gradient. GradientMotion becomes WallpaperMotion, the name of the setting it carries, now that images use it too.
1 parent f0afc23 commit edf03c7

14 files changed

Lines changed: 389 additions & 135 deletions

‎crates/compositor/src/compositor_linux.rs‎

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use crate::frame_geometry::{
4747
tilted_screen_cb, CursorPlacement, CursorPlanInput, FrameGeometryInput, ShadowCaster,
4848
SpriteShape,
4949
};
50-
use crate::scene::{Scene, SceneBackground};
50+
use crate::scene::{Scene, SceneBackground, WallpaperMotion};
5151

5252
const LAYER_WGSL: &str = include_str!("vk_shaders/layer.wgsl");
5353
const BLUR_WGSL: &str = include_str!("vk_shaders/blur.wgsl");
@@ -1472,13 +1472,19 @@ impl Compositor {
14721472
///
14731473
/// Err plutot qu'un repli maison : chaque appelant a son propre message et
14741474
/// son propre repli, et un echec silencieux redonnerait le noir qu'on corrige.
1475+
///
1476+
/// `motion` anime l'image au temps programme `programme_t` ; la bulle webcam
1477+
/// passe `WallpaperMotion::None`.
1478+
#[allow(clippy::too_many_arguments)]
14751479
fn image_bg_draw(
14761480
&self,
14771481
path: &str,
14781482
dst: [f32; 4],
14791483
quad_px: [f32; 2],
14801484
radius_px: f32,
14811485
aspect: f32,
1486+
motion: WallpaperMotion,
1487+
programme_t: f32,
14821488
dummy: &wgpu::TextureView,
14831489
) -> Result<BgDraw> {
14841490
let (tex, iw, ih) = self.cached_image(path)?;
@@ -1491,12 +1497,15 @@ impl Compositor {
14911497
let vis = ai / aspect;
14921498
[0.0, (1.0 - vis) * 0.5, 1.0, 1.0 - (1.0 - vis) * 0.5]
14931499
};
1500+
let (anim, mb) = crate::frame_geometry::wallpaper_motion_slots(motion, programme_t, aspect);
14941501
let cb = LayerCB {
14951502
dst,
14961503
src,
14971504
quad_px,
14981505
radius_px,
14991506
mode: 6.0,
1507+
fx: [0.0, 0.0, anim[0], anim[1]],
1508+
mb,
15001509
..Default::default()
15011510
};
15021511
let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
@@ -1556,11 +1565,12 @@ impl Compositor {
15561565
..crate::frame_geometry::gradient_layer(stops, offsets, BLACK)
15571566
})
15581567
}
1559-
Some(SceneBackground::Image { path }) => {
1568+
Some(SceneBackground::Image { path, .. }) => {
15601569
// Le cover-fit se mesure sur la BULLE, pas sur la sortie : c'est
15611570
// elle que l'image doit remplir sans etirement.
15621571
let aspect = if quad_px[1] > 0.0 { quad_px[0] / quad_px[1] } else { 1.0 };
1563-
match self.image_bg_draw(path, dst, quad_px, radius_px, aspect, dummy) {
1572+
let still = WallpaperMotion::None;
1573+
match self.image_bg_draw(path, dst, quad_px, radius_px, aspect, still, 0.0, dummy) {
15641574
Ok(d) => d,
15651575
Err(e) => {
15661576
// Meme contrat que le fond d'ecran : un chemin casse est
@@ -2087,7 +2097,7 @@ impl Compositor {
20872097
// cfg.bg_blur) floute ensuite ce fond, avant l'ecran.
20882098
enum BgLayer {
20892099
Gradient(LayerCB),
2090-
Image(String),
2100+
Image(String, WallpaperMotion),
20912101
}
20922102
let (bg_clear, bg_layer) = match scene_ref.as_ref().map(|s| s.background.clone()) {
20932103
Some(SceneBackground::Color { color }) => {
@@ -2096,7 +2106,7 @@ impl Compositor {
20962106
Some(SceneBackground::Gradient { angle_deg, stops, offsets, motion }) => {
20972107
let a = angle_deg.to_radians();
20982108
let (anim, mb) =
2099-
crate::frame_geometry::gradient_motion_slots(motion, g.programme_t, rw / rh);
2109+
crate::frame_geometry::wallpaper_motion_slots(motion, g.programme_t, rw / rh);
21002110
let cb = LayerCB {
21012111
dst: [0.0, 0.0, 1.0, 1.0],
21022112
quad_px: [rw, rh],
@@ -2106,8 +2116,8 @@ impl Compositor {
21062116
};
21072117
([0.0, 0.0, 0.0, 1.0], Some(BgLayer::Gradient(cb)))
21082118
}
2109-
Some(SceneBackground::Image { path }) => {
2110-
([0.0, 0.0, 0.0, 1.0], Some(BgLayer::Image(path)))
2119+
Some(SceneBackground::Image { path, motion }) => {
2120+
([0.0, 0.0, 0.0, 1.0], Some(BgLayer::Image(path, motion)))
21112121
}
21122122
None => (lp.bg_color, None),
21132123
};
@@ -2277,10 +2287,10 @@ impl Compositor {
22772287
}
22782288
// Le wallpaper couvre tout le cadre, donc dst plein et pas de coins :
22792289
// `image_bg_draw` sert aussi la bulle webcam, qui elle en a.
2280-
BgLayer::Image(path) => {
2281-
match self
2282-
.image_bg_draw(&path, [0.0, 0.0, 1.0, 1.0], [0.0, 0.0], 0.0, rw / rh, &dummy)
2283-
{
2290+
BgLayer::Image(path, motion) => {
2291+
let full = [0.0, 0.0, 1.0, 1.0];
2292+
let t = g.programme_t;
2293+
match self.image_bg_draw(&path, full, [0.0, 0.0], 0.0, rw / rh, motion, t, &dummy) {
22842294
Ok(d) => Some(d),
22852295
Err(e) => {
22862296
eprintln!("[fond image] \"{path}\" : {e:#}");

‎crates/compositor/src/compositor_macos.rs‎

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub use crate::frame_geometry::{
3737
use crate::frame_geometry::{parse_hex, FrameGeometryInput, ShadowCaster,
3838
SCREEN_SHADOW_SPREAD_FRAC, WEBCAM_SHADOW_OFFSET_FRAC, WEBCAM_SHADOW_OPACITY,
3939
WEBCAM_SHADOW_SPREAD_FRAC};
40-
use crate::scene::{Scene, SceneBackground};
40+
use crate::scene::{Scene, SceneBackground, WallpaperMotion};
4141
use anyhow::{anyhow, Result};
4242
use metal::foreign_types::ForeignType;
4343
use std::cell::RefCell;
@@ -1083,13 +1083,17 @@ impl Compositor {
10831083
enc: &metal::RenderCommandEncoderRef,
10841084
path: &str,
10851085
output_aspect: f32,
1086+
motion: WallpaperMotion,
1087+
programme_t: f32,
10861088
) -> Result<()> {
1087-
self.draw_image_in(enc, path, [0.0, 0.0, 1.0, 1.0], [0.0, 0.0], 0.0, output_aspect)
1089+
let full = [0.0, 0.0, 1.0, 1.0];
1090+
self.draw_image_in(enc, path, full, [0.0, 0.0], 0.0, output_aspect, motion, programme_t)
10881091
}
10891092

10901093
/// `draw_image_bg` pour un rect quelconque — la bulle webcam s'en sert avec ses coins
10911094
/// arrondis. `output_aspect` est le ratio du RECT visé, pas celui de la sortie : le crop
1092-
/// « cover » se calcule contre la zone qu'on remplit.
1095+
/// « cover » se calcule contre la zone qu'on remplit. `motion` anime l'image au temps
1096+
/// programme `programme_t` ; la bulle passe `WallpaperMotion::None`.
10931097
#[allow(clippy::too_many_arguments)]
10941098
unsafe fn draw_image_in(
10951099
&self,
@@ -1099,6 +1103,8 @@ impl Compositor {
10991103
quad_px: [f32; 2],
11001104
radius_px: f32,
11011105
output_aspect: f32,
1106+
motion: WallpaperMotion,
1107+
programme_t: f32,
11021108
) -> Result<()> {
11031109
let (tex, iw, ih) = self.cached_image(path)?;
11041110
let ai = iw as f32 / ih.max(1) as f32;
@@ -1110,6 +1116,7 @@ impl Compositor {
11101116
let vis = ai / ao; // rogne verticalement
11111117
(0.0, (1.0 - vis) * 0.5, 1.0, 1.0 - (1.0 - vis) * 0.5)
11121118
};
1119+
let (anim, mb) = crate::frame_geometry::wallpaper_motion_slots(motion, programme_t, ao);
11131120
enc.set_fragment_texture(2, Some(&tex));
11141121
self.draw_solid(
11151122
enc,
@@ -1119,6 +1126,8 @@ impl Compositor {
11191126
quad_px,
11201127
radius_px,
11211128
mode: 6.0,
1129+
fx: [0.0, 0.0, anim[0], anim[1]],
1130+
mb,
11221131
..Default::default()
11231132
},
11241133
);
@@ -1173,11 +1182,14 @@ impl Compositor {
11731182
},
11741183
);
11751184
}
1176-
Some(SceneBackground::Image { path }) => {
1185+
Some(SceneBackground::Image { path, .. }) => {
11771186
// Même contrat que le fond d'écran : un chemin cassé est loggé puis remplacé par
11781187
// du noir. Un fallback silencieux redonnerait le bug qu'on corrige.
11791188
let aspect = if quad_px[1] > 0.0 { quad_px[0] / quad_px[1] } else { 1.0 };
1180-
if let Err(e) = self.draw_image_in(enc, path, dst, quad_px, radius_px, aspect) {
1189+
let still = WallpaperMotion::None;
1190+
if let Err(e) =
1191+
self.draw_image_in(enc, path, dst, quad_px, radius_px, aspect, still, 0.0)
1192+
{
11811193
eprintln!("[compositor] fond webcam \"{path}\" : {e:#}");
11821194
self.draw_solid(enc, &solid(BLACK));
11831195
}
@@ -2222,7 +2234,7 @@ impl Compositor {
22222234
Some(SceneBackground::Gradient { angle_deg, stops, offsets, motion }) => {
22232235
let a = angle_deg.to_radians();
22242236
let (anim, mb) =
2225-
crate::frame_geometry::gradient_motion_slots(motion, g.programme_t, rw / rh);
2237+
crate::frame_geometry::wallpaper_motion_slots(motion, g.programme_t, rw / rh);
22262238
self.draw_solid(
22272239
enc,
22282240
&LayerCB {
@@ -2233,10 +2245,10 @@ impl Compositor {
22332245
},
22342246
);
22352247
}
2236-
Some(SceneBackground::Image { path }) => {
2248+
Some(SceneBackground::Image { path, motion }) => {
22372249
// Repli couleur en cas d'échec, mais LOGGÉ : un fallback silencieux masquerait
22382250
// un chemin cassé.
2239-
if let Err(e) = self.draw_image_bg(enc, &path, rw / rh) {
2251+
if let Err(e) = self.draw_image_bg(enc, &path, rw / rh, motion, g.programme_t) {
22402252
eprintln!("[compositor] wallpaper image \"{path}\" : {e:#}");
22412253
self.draw_solid(
22422254
enc,

‎crates/compositor/src/compositor_windows.rs‎

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::frame_geometry::{
1616
WEBCAM_SHADOW_OPACITY, WEBCAM_SHADOW_SPREAD_FRAC,
1717
};
1818
use crate::cursor::CursorTrack;
19-
use crate::scene::{Scene, SceneBackground, SceneCrop, SceneCursorSprite};
19+
use crate::scene::{Scene, SceneBackground, SceneCrop, SceneCursorSprite, WallpaperMotion};
2020
use crate::d3d::Gpu;
2121
use crate::ffi::AVFrame;
2222
use anyhow::{bail, Result};
@@ -1085,20 +1085,31 @@ impl Compositor {
10851085
Ok((srv, w, h))
10861086
}
10871087

1088-
unsafe fn draw_image_bg(&self, path: &str, output_aspect: f32) -> Result<()> {
1089-
self.draw_image_in(path, [0.0, 0.0, 1.0, 1.0], [0.0, 0.0], 0.0, output_aspect)
1088+
unsafe fn draw_image_bg(
1089+
&self,
1090+
path: &str,
1091+
output_aspect: f32,
1092+
motion: WallpaperMotion,
1093+
programme_t: f32,
1094+
) -> Result<()> {
1095+
let full = [0.0, 0.0, 1.0, 1.0];
1096+
self.draw_image_in(path, full, [0.0, 0.0], 0.0, output_aspect, motion, programme_t)
10901097
}
10911098

10921099
/// `draw_image_bg` pour un rect quelconque — la bulle webcam s'en sert avec ses coins
10931100
/// arrondis. `output_aspect` est le ratio du RECT visé, pas celui de la sortie : le crop
1094-
/// « cover » se calcule contre la zone qu'on remplit.
1101+
/// « cover » se calcule contre la zone qu'on remplit. `motion` anime l'image au temps
1102+
/// programme `programme_t` ; la bulle passe `WallpaperMotion::None`.
1103+
#[allow(clippy::too_many_arguments)]
10951104
unsafe fn draw_image_in(
10961105
&self,
10971106
path: &str,
10981107
dst: [f32; 4],
10991108
quad_px: [f32; 2],
11001109
radius_px: f32,
11011110
output_aspect: f32,
1111+
motion: WallpaperMotion,
1112+
programme_t: f32,
11021113
) -> Result<()> {
11031114
let (srv, iw, ih) = self.cached_image(path)?;
11041115
let ai = iw as f32 / ih as f32;
@@ -1116,12 +1127,15 @@ impl Compositor {
11161127
let vis = ai / ao; // rogne verticalement
11171128
(0.0, (1.0 - vis) * 0.5, 1.0, 1.0 - (1.0 - vis) * 0.5)
11181129
};
1130+
let (anim, mb) = crate::frame_geometry::wallpaper_motion_slots(motion, programme_t, ao);
11191131
self.upload_cb(&LayerCB {
11201132
dst,
11211133
src: [u0, v0, u1, v1],
11221134
quad_px,
11231135
radius_px,
11241136
mode: 6.0,
1137+
fx: [0.0, 0.0, anim[0], anim[1]],
1138+
mb,
11251139
..Default::default()
11261140
});
11271141
self.ctx.PSSetShaderResources(2, Some(&[Some(srv)]));
@@ -1174,11 +1188,14 @@ impl Compositor {
11741188
..crate::frame_geometry::gradient_layer(stops, offsets, BLACK)
11751189
});
11761190
}
1177-
Some(SceneBackground::Image { path }) => {
1191+
Some(SceneBackground::Image { path, .. }) => {
11781192
// Même contrat que le fond d'écran : un chemin cassé est loggé puis remplacé par
11791193
// du noir. Un fallback silencieux redonnerait le bug qu'on corrige.
11801194
let aspect = if quad_px[1] > 0.0 { quad_px[0] / quad_px[1] } else { 1.0 };
1181-
if let Err(e) = self.draw_image_in(path, dst, quad_px, radius_px, aspect) {
1195+
let still = WallpaperMotion::None;
1196+
if let Err(e) =
1197+
self.draw_image_in(path, dst, quad_px, radius_px, aspect, still, 0.0)
1198+
{
11821199
eprintln!("[compositor] fond webcam \"{}\" : {:#}", path, e);
11831200
self.draw_solid(&solid(BLACK));
11841201
}
@@ -1923,7 +1940,7 @@ impl Compositor {
19231940
// 0° = vers le haut, 90° = vers la droite.
19241941
let a = angle_deg.to_radians();
19251942
let dir = [a.sin(), -a.cos()];
1926-
let (anim, mb) = crate::frame_geometry::gradient_motion_slots(
1943+
let (anim, mb) = crate::frame_geometry::wallpaper_motion_slots(
19271944
motion,
19281945
g.programme_t,
19291946
self.rw() / self.rh(),
@@ -1935,11 +1952,12 @@ impl Compositor {
19351952
..crate::frame_geometry::gradient_layer(&stops, &offsets, lp.bg_color)
19361953
});
19371954
}
1938-
SceneBackground::Image { path } => {
1955+
SceneBackground::Image { path, motion } => {
19391956
// image bg (cover-fit, mise en cache) ; fallback couleur si chargement échoue
19401957
// (loggé — un fallback silencieux masquerait un chemin cassé, cf. le panic
19411958
// borrow qu'on a déjà eu : toute panne doit être visible/traçable).
1942-
if let Err(e) = self.draw_image_bg(&path, self.rw() / self.rh()) {
1959+
let aspect = self.rw() / self.rh();
1960+
if let Err(e) = self.draw_image_bg(&path, aspect, motion, g.programme_t) {
19431961
eprintln!("[compositor] wallpaper image \"{}\" : {:#}", path, e);
19441962
self.draw_solid(&LayerCB {
19451963
dst: [0.0, 0.0, 1.0, 1.0],

‎crates/compositor/src/frame_geometry.rs‎

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,29 +1568,30 @@ fn same_source_path(a: &str, b: &str) -> bool {
15681568
/// Période commune des mouvements de fond (s). Chaque période du shader (6, 10, 12, 15, 20,
15691569
/// 30 s) la divise, donc replier le temps dessus ne crée aucun raccord visible — et garde au
15701570
/// shader un temps borné, là où un `f32` de plusieurs heures perdrait la finesse du bruit.
1571-
pub const GRADIENT_MOTION_PERIOD_S: f32 = 120.0;
1571+
pub const WALLPAPER_MOTION_PERIOD_S: f32 = 120.0;
15721572

1573-
/// Emplacements libres du mode 5 pour le fond animé : `fx.zw` = (temps programme replié, indice
1574-
/// du mouvement), `mb.x` = aspect w/h de la sortie (les nappes de l'aurore restent rondes).
1573+
/// Emplacements libres des modes 5 (dégradé) et 6 (image) pour le fond animé : `fx.zw` = (temps
1574+
/// programme replié, indice du mouvement), `mb.x` = aspect w/h du rect rempli (les nappes de
1575+
/// l'aurore restent rondes).
15751576
///
1576-
/// `GradientMotion::None` rend les zéros d'avant l'animation, emplacement pour emplacement : le
1577-
/// dégradé immobile reste celui d'aujourd'hui, octet pour octet. Fonction pure du temps
1577+
/// `WallpaperMotion::None` rend les zéros d'avant l'animation, emplacement pour emplacement : le
1578+
/// fond immobile reste celui d'aujourd'hui, octet pour octet. Fonction pure du temps
15781579
/// programme, jamais d'un état porté de frame en frame — preview et export, lecture et seek
15791580
/// tombent sur la même image.
1580-
pub fn gradient_motion_slots(
1581-
motion: crate::scene::GradientMotion,
1581+
pub fn wallpaper_motion_slots(
1582+
motion: crate::scene::WallpaperMotion,
15821583
programme_t: f32,
15831584
aspect: f32,
15841585
) -> ([f32; 2], [f32; 4]) {
1585-
use crate::scene::GradientMotion as M;
1586+
use crate::scene::WallpaperMotion as M;
15861587
let index = match motion {
15871588
M::None => return ([0.0, 0.0], [0.0; 4]),
15881589
M::Drift => 1.0,
15891590
M::Aurora => 2.0,
15901591
M::Waves => 3.0,
15911592
};
15921593
(
1593-
[programme_t.rem_euclid(GRADIENT_MOTION_PERIOD_S), index],
1594+
[programme_t.rem_euclid(WALLPAPER_MOTION_PERIOD_S), index],
15941595
[aspect, 0.0, 0.0, 0.0],
15951596
)
15961597
}
@@ -6795,17 +6796,17 @@ mod tests {
67956796
/// Sans mouvement, les emplacements restent les zéros d'avant (dégradé inchangé) ; avec,
67966797
/// le temps est replié sur la période commune et l'indice suit l'ordre du shader.
67976798
#[test]
6798-
fn gradient_motion_slots_are_zero_when_still_and_wrap_the_clock() {
6799-
use crate::scene::GradientMotion as M;
6800-
assert_eq!(gradient_motion_slots(M::None, 37.5, 16.0 / 9.0), ([0.0, 0.0], [0.0; 4]));
6801-
let (fx, mb) = gradient_motion_slots(M::Drift, 125.0, 2.0);
6799+
fn wallpaper_motion_slots_are_zero_when_still_and_wrap_the_clock() {
6800+
use crate::scene::WallpaperMotion as M;
6801+
assert_eq!(wallpaper_motion_slots(M::None, 37.5, 16.0 / 9.0), ([0.0, 0.0], [0.0; 4]));
6802+
let (fx, mb) = wallpaper_motion_slots(M::Drift, 125.0, 2.0);
68026803
assert_eq!(fx, [5.0, 1.0]);
68036804
assert_eq!(mb, [2.0, 0.0, 0.0, 0.0]);
6804-
assert_eq!(gradient_motion_slots(M::Aurora, 0.0, 1.0).0[1], 2.0);
6805-
assert_eq!(gradient_motion_slots(M::Waves, 240.0, 1.0).0, [0.0, 3.0]);
6805+
assert_eq!(wallpaper_motion_slots(M::Aurora, 0.0, 1.0).0[1], 2.0);
6806+
assert_eq!(wallpaper_motion_slots(M::Waves, 240.0, 1.0).0, [0.0, 3.0]);
68066807
// Chaque période du shader divise la période de repli : pas de raccord au bouclage.
68076808
for p in [6.0, 10.0, 12.0, 15.0, 20.0, 30.0] {
6808-
assert_eq!(GRADIENT_MOTION_PERIOD_S % p, 0.0, "période {p}");
6809+
assert_eq!(WALLPAPER_MOTION_PERIOD_S % p, 0.0, "période {p}");
68096810
}
68106811
}
68116812

0 commit comments

Comments
 (0)