From eee2b415d2eeac99c70e23fed55343599058d35c Mon Sep 17 00:00:00 2001 From: Sophie Herold Date: Tue, 23 Jun 2026 22:45:16 +0200 Subject: [PATCH] image/loading: Adjust frame timing by accrued delay This should fix animations systematically slowing down. --- .../fixed-animation-dont-accumulate-delay | 1 + src/decoder/tiling.rs | 18 ++++++++++-------- src/widgets/image.rs | 4 ++-- src/widgets/image/loading.rs | 17 +++++++++++++---- 4 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 news.d/51.alpha/fixed-animation-dont-accumulate-delay diff --git a/news.d/51.alpha/fixed-animation-dont-accumulate-delay b/news.d/51.alpha/fixed-animation-dont-accumulate-delay new file mode 100644 index 0000000..00b3ae5 --- /dev/null +++ b/news.d/51.alpha/fixed-animation-dont-accumulate-delay @@ -0,0 +1 @@ +Don't accumulate delays during animations. !596 diff --git a/src/decoder/tiling.rs b/src/decoder/tiling.rs index 48d4bed..a4d474e 100644 --- a/src/decoder/tiling.rs +++ b/src/decoder/tiling.rs @@ -649,22 +649,24 @@ impl SharedFrameBuffer { tiling.unwrap() } - /// Return true if the next frame should be shown and removes the outdated - /// frame - pub fn frame_timeout(&self, elapsed: Duration) -> bool { + /// Remove current frame if frame delay elapsed + /// + /// Returns the amount of time the frame is shown 'too late' + pub fn frame_timeout(&self, elapsed: Duration) -> Option { let images = &self.buffer.load().images; // Only move to next image if there is one buffered if images.len() > 1 - && images - .front() - .is_some_and(|next_frame| elapsed >= next_frame.delay) + && let Some(next_frame) = images.front() + && elapsed >= next_frame.delay { + let delay = elapsed - next_frame.delay; self.next_frame(); - return true; + let delay = delay.min(next_frame.delay); + return Some(delay); } - false + None } pub fn next_frame(&self) { diff --git a/src/widgets/image.rs b/src/widgets/image.rs index a2adac5..64059c5 100644 --- a/src/widgets/image.rs +++ b/src/widgets/image.rs @@ -253,8 +253,8 @@ mod imp { /// Ticks callback for animated image formats pub(super) tick_callback: RefCell>, - /// Frame block time for currently shown frame - pub(super) last_animated_frame: Cell, + /// Adjusted frame clock time for when the current frame was shown + pub(super) last_animated_frame: Cell>, /// Gesture, zoom or rotate, used for the duration of the gesture pub(super) locked_gestured: Cell>, diff --git a/src/widgets/image/loading.rs b/src/widgets/image/loading.rs index e910086..39a215b 100644 --- a/src/widgets/image/loading.rs +++ b/src/widgets/image/loading.rs @@ -160,17 +160,26 @@ impl imp::LpImage { // Do not animate if not visible if !obj.is_mapped() { + self.last_animated_frame.set(None); return glib::ControlFlow::Continue; } - let elapsed = clock.frame_time() - self.last_animated_frame.get(); - let duration = std::time::Duration::from_micros(elapsed as u64); + let duration = if let Some(last_animated_frame) = self.last_animated_frame.get() { + let elapsed = clock.frame_time() - last_animated_frame; + std::time::Duration::from_micros(elapsed as u64) + } else { + self.last_animated_frame.set(Some(clock.frame_time())); + std::time::Duration::ZERO + }; // Check if it's time to show the next frame - if self.frame_buffer.frame_timeout(duration) { + if let Some(delay) = self.frame_buffer.frame_timeout(duration) { // Just draw since frame_timeout updated to new frame obj.queue_draw(); - self.last_animated_frame.set(clock.frame_time()); + // Adjust the time when the frame was shown by how much anwanted delay we + // accumulated + self.last_animated_frame + .set(Some(clock.frame_time() - delay.as_micros() as i64)); if let Some(decoder) = self.decoder.borrow().as_ref() { // Decode new frame and load it into buffer decoder.fill_frame_buffer();