You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
crates/rustmotion-components/src/gif.rs stores every frame as (frame.buffer, gif_width, gif_height), where gif_width/height come from decoder.width()/height() — the logical canvas. But an optimised GIF encodes each frame after the first as a sub-rectangle (frame.left/top/width/height), so frame.buffer is smaller than gif_width * gif_height * 4.
At paint time the ImageInfo declares the canvas size with row_bytes = gif_width * 4, so skia_safe::images::raster_from_data gets a buffer shorter than it needs, returns None, and the if let Some(img) arm is simply skipped — nothing is drawn and nothing is said. Frame 0 is full-canvas in most encoders, which is why it is the only one that appears.
Proposed fix
Compose frames instead of storing them raw. Keep one full-canvas RGBA buffer; for each decoded frame, blit frame.buffer at (frame.left, frame.top) over frame.width × frame.height, then snapshot it. This also fixes correctness beyond the crash: even at the right size, an optimised frame only carries the pixels that changed, so drawing it alone would show a fragment on an empty background.
Honour frame.dispose (Keep / Background / Previous) when composing, otherwise frames leak into each other.
waveform and audio_spectrum have pixel-level tests; gif has none. A test rendering a two-frame GIF whose second frame is a sub-rectangle, asserting both frames differ and neither is empty, would have caught this.
Symptom
A
gifcomponent shows its first frame and then nothing. No error, no warning — the scenario validates and the render succeeds.Reproduction
Cause
crates/rustmotion-components/src/gif.rsstores every frame as(frame.buffer, gif_width, gif_height), wheregif_width/heightcome fromdecoder.width()/height()— the logical canvas. But an optimised GIF encodes each frame after the first as a sub-rectangle (frame.left/top/width/height), soframe.bufferis smaller thangif_width * gif_height * 4.At paint time the
ImageInfodeclares the canvas size withrow_bytes = gif_width * 4, soskia_safe::images::raster_from_datagets a buffer shorter than it needs, returnsNone, and theif let Some(img)arm is simply skipped — nothing is drawn and nothing is said. Frame 0 is full-canvas in most encoders, which is why it is the only one that appears.Proposed fix
frame.bufferat(frame.left, frame.top)overframe.width × frame.height, then snapshot it. This also fixes correctness beyond the crash: even at the right size, an optimised frame only carries the pixels that changed, so drawing it alone would show a fragment on an empty background.frame.dispose(Keep/Background/Previous) when composing, otherwise frames leak into each other.File::open,read_info, and the droppedraster_from_data) should surface the way fix(studio): re-analyse audio on every scenario load, and report failures #181 made the audio analysis surface its failures, rather thanreturn.Test
waveformandaudio_spectrumhave pixel-level tests;gifhas none. A test rendering a two-frame GIF whose second frame is a sub-rectangle, asserting both frames differ and neither is empty, would have caught this.