lehitoskin / ivy

Ivy, the Taggable Image Viewer
GNU General Public License v3.0
16 stars 3 forks source link

GIF animation #34

Closed IonoclastBrigham closed 8 years ago

IonoclastBrigham commented 8 years ago

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.

lehitoskin commented 8 years ago

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]

IonoclastBrigham commented 8 years ago

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?

lehitoskin commented 8 years ago

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.

lehitoskin commented 8 years ago

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.

IonoclastBrigham commented 8 years ago

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!)

lehitoskin commented 8 years ago

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?

IonoclastBrigham commented 8 years ago

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.

IonoclastBrigham commented 8 years ago

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:

lehitoskin commented 8 years ago

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.

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.

IonoclastBrigham commented 8 years ago

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.

lehitoskin commented 8 years ago

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

lehitoskin commented 8 years ago

Fulfilled in 836ff0f5769f8e780fd52947821b7768367bf4f1