Closed chregu closed 4 years ago
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.
Oh haha, and the page-height
is wrong, oh dear. I'll fix that at least.
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.
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.
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]);
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 ;))
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?
The original btw shows the "right" gif-delay value, I uploaded it here: https://github.com/jcupitt/php-vips-ext/issues/16#issuecomment-346008940 (linking to avoid more nervousness here ;))
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?
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.
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.
@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.
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.
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.
OK, we have gif-loop
as well.
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/
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
Argh I'd misunderstood how extension blocks worked. It seems to be OK now.
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 ;))
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
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.
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?
OK, only the first delay is reported now.
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.
This is good enough for now. Closing issue finally.
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
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.