lehitoskin/ivy

GIF animation

Closed this issue · 12 comments

GIFs really should animate. I realize this is a limitation of how the app is structured around the canvas and how images are loaded into it, and thus might be some time before a suitable solution is found.

Please post relevant links below.

I once made a script that drew each frame of a dissected gif onto a canvas such that after every 0.5 seconds it would load the next frame; however, this was only possible because I had manually dissected the gif beforehand. I remember bringing up this issue before with the people in the Racket IRC channel, but they just let me in on displaying animation in canvases by setting it to an infinite loop.

e.g.

(send canvas set-on-paint!
      (λ ()
        (define dc (send canvas get-dc))
        (define frames (list "frame1.jpg" "frame2.jpg" "frame3.jpg" "frame4.jpg"))
        (define len (length frames))
        (define bmp (make-object bitmap% 50 50))
        (let loop ([frame (first frames)]
                   [i 0])
          (send bmp load-file frame)
          (send dc draw-bitmap bmp 0 0)
          (sleep 1/2)
          (if (>= i len)
              (loop (list-ref frames 0) 0)
              (loop (list-ref frames i) (add1 i))))))

The on-paint procedure could be put in a separate thread and then we could kill the thread once we want to move to another image --- or do anything else. The biggest problem would be getting those frames out of that gif.

The libraries, mrlib/gif and file/gif, are related to gifs but the first one is for saving to disk and the second one is confusing to reason about.

[Edit: library ordering]

Possibly we could use (or extract code from) this project: https://github.com/cbowdon/gif-image The last update was from 2012, so it might require some coddling to get it working with Racket 6.x, but possibly we could fork and take over this project too, if it seems salvageable as a whole, rather than just 1 or 2 files or functions.

From the README:

There's plenty of "make animated GIF" apps around, but none that let you split an existing animated GIF up, play around with it and then reassemble it... Racket already includes good procedures for creating GIFs (the file/gif module) but none for taking them apart. So this is something new.

I haven't looked closely at this, but I don't think it handles putting the animation on the screen. That still leaves us to design and implement a robust gif renderer, but I think that could be a valuable spinoff project in and of itself, based on how little my initial searches have turned up.

My first impulse is to subclass and extend canvas% and/or bitmap%. The exact implementation would depend on exactly how we loaded and manipulated the frame data. But I think a gif-aware extension to canvas% would be a pretty swell thing to have. If you intercept calls to load-image or whatever it is, you don't have to worry about synchronizing a render thread with every single event that tries to shove a png on the screen, because the canvas would manage that internally. And I think we can do a lot better than "draw a frame every half second" if we have access to the actual embedded frame timing (which I'm just assuming we'll be able to extract).

Thoughts?

That's an excellent find! I should have searched through github looking for something myself :P

Hmmm... Perhaps a subclass of bitmap% where we could add 'gif/animation to the list of accepted kind s. A subclass of canvas% would also probably need a subclass of dc%, since that's what we talk to when we want to draw-bitmap or draw-pict.

I pushed my WIP code to the animated-gif branch (which uses my fork of the gif-image library). Check it out, but make sure to run it from a terminal. DrRacket will simply error with "out of memory", but running from the terminal will give different errors.

So as you'd indicated elsewhere, this works wonderfully, when it works. Some files give weird results. And some just throw an error and break the app.

As such, personally I'd be okay with making this perhaps an optional feature. We can catch those errors, and just fall back on the old way of displaying the first frame if the animation fails.
Perhaps a new "View > GIF Animation" toggle-able menu item, disabled by default.

This would be a good candidate for integration into a more comprehensive Error / Alert system in the UI, so the user knows that yes, we're trying to load the animation, but we're just showing the first frame because of an error we caught. Bonus points if this is easily visible but out of the way and requires no interaction (i.e. not a modal dialog!)

Perhaps the message could be placed in the status bar, right in the middle? Something along the lines of "GIF fallback mode".

As for the error/alert system, what I had in mind was indeed a dialog box - do you think that's the wrong way to go?

Well I think we'd just want a little indicator somewhere on the UI. A modal would be super annoying if it popped every time you failed to animate a gif, especially if you were just trying to skip through a huge folder of them or something.

Perhaps we're talking about two different systems here, to communicate with the user in different ways.

So gif-image's support for the gif standard seems pretty incomplete, or perhaps there's some common but off-spec encoders out there that it's not handling gracefully. In either case it's failing on files that work just fine basically everywhere else, which is unacceptable. That leaves us to either (a) expand / debug the library ourselves, probably requiring a lot of hours tinkering and poring through file format definition documents, or (b) find a replacement.

It's probably worth seeing if it can be fixed easily, but my inclination is toward (b). Based on my initial search, it doesn't seem like there's much out there to be had, for Racket. Thus, we'll likely need to find something written in C and use FFI to wrap it in a tidy racket interface like what you did for SVG support.

So, a few questions for you, @lehitoskin:

  • What would be the requirements of such a library?
  • What kind of data would you need to get back from it, to load frames into a pict% / bitmap% / whatever?
  • Do we just want an array/list of frame pixels so we can do whatever we need to with them, or would it be nice to have something that will handle the animation thread for us, maybe with a draw callback?
  • Is it even possible to draw into a canvas% / dc% from C code, or would we need to call across the C/Racket barrier into a function that would do the drawing for us?

I messaged the owner of gif-image some time ago about implementing the optimization detection, but he hasn't responded. I recoil at the thought of teaching myself the GIF spec when so much work has been done by others already, so unless or until cbowdon responds and can add what we need, a C library is probably the next best thing.

  • Hopefully the library has few dependencies itself (for packaging with Windows)
  • We would need to get a byte string back from the library, then we can load it into a bitmap or pict
  • A list of frame pixels would be the simplest - gif-image allows for us to get the individual frame delays, so if the library can do that as well, that would be excellent. I imagine if it cannot, then we can supplement that by keeping gif-image for that alone.
  • Drawing to a canvas% or a dc% from C is "probably" possible, but the amount of work required wouldn't be worth it, I think. Having a library that returns a list (or array) of frame bytes would be easiest for me to work with.

As for what library we might look into, with zero investigation I'm going to give libvips an honorable mention. It supports a whole bunch of different formats including GIF.

Oh, and it looks like libvips already has SWIG integration. It might be pretty simple to get it to generate the Racket bindings for us.

lol jk. I just looked at the hex for a few GIF files and accidentally stumbled upon the Graphics Control Extension.

Fulfilled in 836ff0f