Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

PillowWriter GIF duration and loop #19169

Copy link
Copy link
Closed
@znstrider

Description

@znstrider
Issue body actions

Current PillowWriter functionality does not allow me to specify the duration for each of the gif frames and it is not possible to not have the gif loop.

Additionally, I frequently get a ValueError: buffer is not large enough when animating multi axes figures. (Not sure if this is a common problem).

Animation to help introduce a visual story can be quite beneficial to understanding and for that I would like to be able to not loop the gif and "pause" the gif via different durations for all frames.

Proposed Solution

  1. add duration=None, loop=0 arguments to setup() and to a PillowWriter specific saving context_manager.
    To not loop we can specify loop=None and then we specifically need to not pass the loop argument to save() in finish().

  2. What is the difference between Image.frombuffer and Image.open(buf)? I find that Image.open(buf) solves my problem of the ValueError: buffer is not large enough.
    It seems to take more time for me though. (about +33%)

  3. removed unused variable assignment: renderer = self.fig.canvas.get_renderer() from grab_frame()

class PillowWriter(AbstractMovieWriter):
    @classmethod
    def isAvailable(cls):
        return True

    def setup(self, fig, outfile, dpi=None, duration=None, loop=None):
        super().setup(fig, outfile, dpi=dpi)
        self.duration = duration
        self.loop = loop
        self._frames = []

    def grab_frame(self, **savefig_kwargs):
        buf = BytesIO()
        self.fig.savefig(
            buf, **{**savefig_kwargs, "dpi": self.dpi})
        self._frames.append(Image.open(buf))

    def finish(self):
        duration = self.duration or int(1000 / self.fps)

        # to not loop at all we need to avoid passing the loop argument to save
        if self.loop is None:
            self._frames[0].save(
                self.outfile, save_all=True, append_images=self._frames[1:],
                duration=duration)
        else:
            self._frames[0].save(
                self.outfile, save_all=True, append_images=self._frames[1:],
                duration=duration, loop=self.loop)

    @contextlib.contextmanager
    def saving(self, fig, outfile, dpi, duration=None, loop=0, *args, **kwargs):
        """
        Context manager to facilitate writing the movie file.
        ``*args, **kw`` are any parameters that should be passed to `setup`.
        """
        # This particular sequence is what contextlib.contextmanager wants
        self.setup(fig, outfile, dpi, duration, loop, *args, **kwargs)
        try:
            yield self
        finally:
            self.finish()

Additional context and prior art

endless looping for gifs was introduced here:
#11789

Documentation would have to be updated I suppose.
If this seems like a useful improvement to the PillowWriter I could go ahead and create a PR.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      Morty Proxy This is a proxified and sanitized view of the page, visit original site.