Skip to content

gif: only the first frame renders, the rest silently draw nothing #185

Description

@LeadcodeDev

Symptom

A gif component shows its first frame and then nothing. No error, no warning — the scenario validates and the render succeeds.

Reproduction

ffmpeg -f lavfi -i "testsrc2=size=64x64:rate=12:duration=2" anim.gif   # 24 frames
# scenario: { "type": "gif", "src": "<abs>/anim.gif", "style": {"width":128,"height":128} }
rustmotion render -f gif.json -o f.png --frame 0    # 16140 non-black pixels
rustmotion render -f gif.json -o f.png --frame 5    # 0 non-black pixels

Cause

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

  1. 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.
  2. Honour frame.dispose (Keep / Background / Previous) when composing, otherwise frames leak into each other.
  3. Say something when a decode fails. The three silent exits (File::open, read_info, and the dropped raster_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 than return.

Test

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.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions