Skip to content

[p5.js 2.0+ Bug Report]: saveGif: delay option teleports frameCount instead of rendering warmup frames #8888

@MattKaneArtist

Description

@MattKaneArtist

Most appropriate sub-area of p5.js?

  • Accessibility
  • Color
  • Core/Environment/Rendering
  • Data
  • DOM
  • Events
  • Image
  • IO
  • Math
  • Typography
  • Utilities
  • WebGL
  • WebGPU
  • p5.strands
  • Build process
  • Unit testing
  • Internationalization
  • Friendly errors
  • Other (specify if possible)

p5.js version

2.0.5

Web browser and version

149.0.7827.54 (Official Build) (64-bit) (cohort: Stable)

Operating system

Windows 11 Pro

Steps to reproduce this

Steps:

  1. Run the sketch below and wait until frameCount exceeds 300 (canvas turns dark, circle grows large)
  2. Note the frame number displayed on screen — this is where the animation currently is
  3. Click "Save GIF", then open the downloaded file
  4. Observe: the first frames of the GIF show a bright background, tiny circle, and a frame number near the delay value — not where the animation actually was

Snippet:

<!DOCTYPE html>
<html>
<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/2.0.5/p5.min.js"></script>
</head>
<body>
<script>
  function setup() {
    createCanvas(480, 480);
    colorMode(HSB, 360, 100, 100);
    noStroke();
    let btn = createButton('Save GIF');
    btn.mousePressed(() => saveGif('demo', 3, { units: 'frames', delay: 30 }));
  }

  function draw() {
    let brightness = map(frameCount, 0, 600, 95, 5, true);
    let saturation = map(frameCount, 0, 600, 10, 90, true);
    background((frameCount * 0.3) % 360, saturation, brightness);
    let sz = map(frameCount, 0, 600, 20, 380, true);
    fill(((frameCount * 0.3) + 180) % 360, 80, 100);
    ellipse(width/2, height/2, sz);
    fill(0, 0, brightness > 50 ? 10 : 95);
    textSize(18);
    textAlign(CENTER, CENTER);
    text('frame ' + frameCount, width/2, height/2);
  }
</script>
</body>
</html>

Description:

saveGif has two related bugs in the same block of code that together make the delay option actively harmful rather than helpful.

Bug 1 — frameCount is overwritten with the delay value

Before recording begins, saveGif does:

let frameIterator = nFramesDelay;
this.frameCount = frameIterator; // ← line 39298 in 2.0.5
This teleports the sketch's frameCount to the delay value. If the animation has been running for 400 frames and you call saveGif('out', 3, { delay: 5, units: 'frames' }), the sketch's frameCount is set to 5 and the GIF renders from the animation's frame-5 state — not from frame 400 where you actually were.

Bug 2 — the delay renders zero warmup frames

The capture loop starts at frameIterator = nFramesDelay and runs until frameIterator < totalNumberOfFrames (nFrames + nFramesDelay). That's exactly nFrames iterations — every one of which is captured. There are no un-captured pre-capture renders. The delay parameter only changes where frameIterator starts counting; it never actually renders any warmup frames before capture begins.

This means the two bugs compound: even if you try to use a large delay to "skip past" the bad frames, Bug 1 ensures those frames are now from the wrong point in the animation entirely.

Why it matters: Coders often utilize frameCount as a variable guiding some aspect of their visuals. Because saveGif resets frameCount, that means the sketch gets set back to the first frame despite calling saveGif elsewhere. So the intended visuals are not saved. AND - the delay doesn't work as documentation intends.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions