Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add animated gif support #3

Closed
chregu opened this issue Nov 21, 2017 · 24 comments
Closed

Add animated gif support #3

chregu opened this issue Nov 21, 2017 · 24 comments

Comments

@chregu
Copy link
Contributor

chregu commented Nov 21, 2017

As libvips doesn't support animated gifs, we maybe can fallback to gifsicle for this.

Example code for creating one from an existing animated gif

    $im = new \Imagine\Vips\Imagine();
    $ori = $im->open("animated.gif");
    $i=0;
    /**
     * parse colors, loop for global options and disposal, delay from each frame with
     * gifsicle --info first and use them later instead of hardcoding
     */
    $options = "gifsicle -v --colors 256 --loop=forever -O3";
    /** @var \Imagine\Vips\Image $merged */
    foreach ($ori->layers() as $layer) {

        //merge each layer into the merged image to not have ugly transparency effects when doing operations
        if ($i > 0) {
            $merged = $merged->paste($layer, new Point(0, 0));
        } else {
            $merged = $layer;
        }
        $frame = clone $merged;
        // do the actual operation
        $frame->effects()->negative();
        $frame->save("anim$i.gif");
        $options .= " --delay 4 anim$i.gif";
        $i++;
    }

    $command =  $options . " -o animated.new.gif \n";
    echo $command;
    // run the gifsicle command
    echo `$command`;

We could of course also use imagick to do something similar instead of gifsicle, eg. the identify -verbose command (also available in PHP imagick) gives you also all the "delay, loop" information one needs, see http://www.imagemagick.org/Usage/scripts/gif2anim for inspiration. And maybe better to rely on a php extension than a CLI. gifsicle of course makes the more optimized gifs, but that could be another step.

@chregu chregu mentioned this issue Nov 21, 2017
@jcupitt
Copy link

jcupitt commented Nov 21, 2017

libvips can load all the frame in an animated GIF with n=-1, it's saving that's not supported. Looking at the output, it doesn't seem to note the frame times, that's something we should fix:

$ vipsheader -a image006.gif[n=-1]
image006.gif: 169x2750 uchar, 4 bands, srgb, gifload
width: 169
height: 2750
bands: 4
format: uchar
coding: none
interpretation: srgb
xoffset: 0
yoffset: 0
xres: 1
yres: 1
filename: image006.gif
vips-loader: gifload
page-height: 2750

I think the big problem with write is generating the best palette, and perhaps doing inter-frame optimisation. We should probably add a dependency to a specialist library for this.

If we just want a simple, quick hack, we could make a 3:3:2 cube palette and dither with that.

We could also record the palette used for load, and recode with that on write. This could be a bit slow though: you'd probably need to build a large table to avoid a search for each pixel.

@jcupitt
Copy link

jcupitt commented Nov 21, 2017

Oh haha, and the page-height is wrong, oh dear. I'll fix that at least.

@jcupitt
Copy link

jcupitt commented Nov 21, 2017

OK, I now see:

$ vipsheader -a image006.gif[n=-1]
image006.gif: 169x2750 uchar, 4 bands, srgb, gifload
width: 169
height: 2750
bands: 4
format: uchar
coding: none
interpretation: srgb
xoffset: 0
yoffset: 0
xres: 1
yres: 1
filename: image006.gif
vips-loader: gifload
page-height: 125
gif-delay: 12

There should be a gif-loops field too.

@chregu
Copy link
Contributor Author

chregu commented Nov 21, 2017

So, there's basic animated gif support now, but you need to fallback to the imagick library (in imagine) to use this which seems the more proper way than integrating that in the vips adapter itself.

To use it, do it like that:

(most operations on the library don't support layers yet, but that's easy to change)

$im = new \Imagine\Vips\Imagine();
$ori = $im->open("animated.gif");
$ori->layers()->coalesce();
$resized = $ori->resize(new Box(200,200));
$imagick = $resized->convertToAlternative();
$imagick->save("test.gif" ,['animated' => true, 'animated.delay' => 4]);

It's currently only in layers-support branch.

The gif won't be highly optimized, but good enough for later optimization.

@chregu
Copy link
Contributor Author

chregu commented Nov 21, 2017

gif-delay: 12
Cool, that is useful, a gif-loops would be great too

We could at least do now this in the above example

$delay = 10;

try {
    //try to get the original delay
    $delay = $ori->getVips()->get('gif-delay');
} catch (\Jcupitt\Vips\Exception $e) {}
$imagick->save("test.gif" ,['animated' => true, 'animated.delay' => $delay]);

@chregu
Copy link
Contributor Author

chregu commented Nov 21, 2017

Tested now that gif-loops value. I assume the "unit" of that is "ticks" and a second has 100 ticks in gif (so the unit is "10ms").

But with playing around with it, I encountered something strange.

I have this image (sorry for the nervous thing, should find a better one ;))

test

I set the delay to 40ms (or 4 ticks), which is what the original uses. imagemagick identify -verbose also reports that for the frames. But vipsheader tells me gif-delay: 16

Bug or is imagemagick not setting some global delay correctly?

@chregu
Copy link
Contributor Author

chregu commented Nov 21, 2017

The original btw shows the "right" gif-delay value, I uploaded it here: libvips/php-vips-ext#16 (comment) (linking to avoid more nervousness here ;))

@jcupitt
Copy link

jcupitt commented Nov 21, 2017

libvips is just attaching the number in the gif header, which I think is 1/100ths of a second. I agree, I see something different from identify.

What is "ticks", exactly? Could it be 1/60ths of second, the frame time for most displays?

@chregu
Copy link
Contributor Author

chregu commented Nov 21, 2017

No idea where the ticks come from, just saw it here
http://php.net/manual/en/imagick.setimagedelay.php

Sets the image delay. For an animated image this is the amount of time that this frame of the image should be displayed for, before displaying the next frame.

@dbu
Copy link

dbu commented Nov 21, 2017

just some random input without having followed everything: i remember from creating animations that each layer can have an individual time how long its displayed. afaik there is no regular clock interval in an animated gif. but maybe imagemagick does something extra on the way to convert to movie formats.

@chregu
Copy link
Contributor Author

chregu commented Nov 21, 2017

@dbu at least the Imagine layer only has a global delay option. But you're right that an animated gif can have different delays per frame.

@jcupitt
Copy link

jcupitt commented Nov 21, 2017

It looks like ticks defaults to 1/100th, but you can change it.

https://www.imagemagick.org/discourse-server/viewtopic.php?t=14739

I can't get identify to report the delay consistently. For your GIF I see:

john@kiwi:~/pics$ identify nervous.gif 
nervous.gif[0] GIF 550x400 550x400+0+0 8-bit sRGB 128c 58.2KB 0.000u 0:00.000
nervous.gif[1] GIF 365x319 550x400+37+81 8-bit sRGB 128c 58.2KB 0.000u 0:00.000
nervous.gif[2] GIF 379x328 550x400+30+72 8-bit sRGB 128c 58.2KB 0.000u 0:00.000
nervous.gif[3] GIF 448x331 550x400+30+69 8-bit sRGB 128c 58.2KB 0.000u 0:00.000
nervous.gif[4] GIF 403x331 550x400+98+69 8-bit sRGB 128c 58.2KB 0.000u 0:00.000
nervous.gif[5] GIF 351x314 550x400+150+86 8-bit sRGB 128c 58.2KB 0.000u 0:00.000
nervous.gif[6] GIF 418x336 550x400+66+64 8-bit sRGB 128c 58.2KB 0.000u 0:00.000
nervous.gif[7] GIF 459x336 550x400+25+64 8-bit sRGB 128c 58.2KB 0.000u 0:00.000
john@kiwi:~/pics$ vipsheader -a nervous.gif 
nervous.gif: 550x400 uchar, 4 bands, srgb, gifload
width: 550
height: 400
bands: 4
format: uchar
coding: none
interpretation: srgb
xoffset: 0
yoffset: 0
xres: 1
yres: 1
filename: nervous.gif
vips-loader: gifload
gif-delay: 4

I'm not sure what to do now. I'll have a look at looping instead.

@chregu
Copy link
Contributor Author

chregu commented Nov 21, 2017

maybe imagemagick sets some global header to some value noone else is actually using.
To reproduce without php imagick, but imagemagick cli and gifsicle

$ vipsheader -f gif-delay animated.gif
4

$ convert animated.gif test.gif ; vipsheader -f gif-delay test.gif
16

$ gifsicle animated.gif -o test2.gif ; vipsheader -f gif-delay test2.gif
4

$ gifsicle test.gif -o test3.gif; vipsheader -f gif-delay test3.gif
16

$ identify -verbose test.gif  | grep Delay | head -1
  Delay: 4x100

$ identify -verbose test2.gif  | grep Delay | head -1
  Delay: 4x100

$ identify -verbose test3.gif  | grep Delay | head -1
  Delay: 4x100

vipsheader also reports 16 when I convert another image with another delay.

jcupitt added a commit to libvips/libvips that referenced this issue Nov 21, 2017
@jcupitt
Copy link

jcupitt commented Nov 21, 2017

OK, we have gif-loop as well.

@jcupitt
Copy link

jcupitt commented Nov 21, 2017

16 was the default value vips was using. I've changed it to default to 4, so it should match IM.

Thanks for the -verbose tip -- I now see a match:

john@kiwi:~/pics$ vipsheader -f gif-delay nervous.gif 
4
john@kiwi:~/pics$ vipsheader -f gif-delay 3198.gif 
3
john@kiwi:~/pics$ identify -verbose nervous.gif | grep Delay | head -1
  Delay: 4x100
john@kiwi:~/pics$ identify -verbose 3198.gif | grep Delay | head -1
  Delay: 3x100

\o/

@chregu
Copy link
Contributor Author

chregu commented Nov 21, 2017

Delay looks fine now after some quick tests. Loop although always reports '0' for me (again with convert by imagemagick)

$ convert -loop 3 animated.gif  test.gif
$ vipsheader -a  -f gif-loop test.gif
0

@jcupitt
Copy link

jcupitt commented Nov 21, 2017

Argh I'd misunderstood how extension blocks worked. It seems to be OK now.

@chregu
Copy link
Contributor Author

chregu commented Nov 21, 2017

Looks better, but is there an off-by-one error now?

$ convert -loop 5 animated.gif test.gif; vipsheader -a  -f gif-loop test.gif
4
$ convert -loop 1 animated.gif test.gif; vipsheader -a  -f gif-loop test.gif
0
$ convert -loop 0 animated.gif test.gif; vipsheader -a  -f gif-loop test.gif
0

(the last one is correct ;))

@chregu
Copy link
Contributor Author

chregu commented Nov 22, 2017

oh, found another issue with gif-delay:

following code for http://files.chregu.tv/liip-blog-animated.gif

$image = \Jcupitt\Vips\Image::newFromFile("liip-blog-animated.gif", ['n' => -1]);
print $image->get("gif-delay") ."\n";

Result: 116 (there are 96 frames)

Without ['n' => -1]

$image = \Jcupitt\Vips\Image::newFromFile("liip-blog-animated.gif");
print $image->get("gif-delay") ."\n";

Result the to be expected 4

vipsheader also delivers the correct 4

@jcupitt
Copy link

jcupitt commented Nov 22, 2017

gif-loop seems to be working for me. I see:

john@kiwi:~/pics$ convert -loop 5 3198.gif x.gif
john@kiwi:~/pics$ vipsheader -a x.gif
x.gif: 298x193 uchar, 3 bands, srgb, gifload
width: 298
height: 193
bands: 3
format: uchar
coding: none
interpretation: srgb
xoffset: 0
yoffset: 0
xres: 1
yres: 1
filename: x.gif
vips-loader: gifload
gif-delay: 3
gif-loop: 5

That's with IM 7.0.3, if that makes any difference.

@jcupitt
Copy link

jcupitt commented Nov 22, 2017

gifload is reporting the last delay it saw, and the final frame of your logo animation has a time of 116.

Perhaps it should only report the first time?

@jcupitt
Copy link

jcupitt commented Nov 22, 2017

OK, only the first delay is reported now.

@chregu
Copy link
Contributor Author

chregu commented Nov 22, 2017

Oh, didn't check that the last delay is longer than the first. Sorry about that, but certainly makes more sense to report the first one.

@chregu
Copy link
Contributor Author

chregu commented Jan 8, 2020

This is good enough for now. Closing issue finally.

@chregu chregu closed this as completed Jan 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants