image-gif icon indicating copy to clipboard operation
image-gif copied to clipboard

Improve the efficiency of encoder

Open SeaDve opened this issue 2 years ago • 3 comments

Here's the comparison of ffmpeg and image-gif relative to source mp4 video All in 30 FPS, 640x360 resolution


source mp4

https://user-images.githubusercontent.com/64297935/125145565-3f463400-e154-11eb-93d2-319daa87067b.mp4

size = 913.5 KB


ffmpeg's gif

ffmpeg

size = 887.4 KB


image-gif's gifenc

image-rs

size = 2.5 MB

SeaDve avatar Jul 09 '21 23:07 SeaDve

There's a really intriguing color difference between the two images. ffmpeg has inserted some sort of dithering in some of the solid color blocks. Other than that defect, the big difference comes from delta-encoding. The individual frames of ffmpeg use this canvas:

canvas: 160x93+480+267

Which is merely the changing square at the bottom right. The image-rs encoder does not try to detect where delta encoding could be used so it rewrites the full image for every frame—just like the user is asking for when providing the Frame instance. For the moment you can probably throw any gif optimizer on it to rectify the difference. gifski might also work for this.

However given that this might a somewhat common occurance I'm going to keep this open as a starting point if someone wants to add an optional layer that can be enabled to optimize away these 'obvious' repeating pixels somewhere in Encoder.

HeroicKatora avatar Sep 04 '21 23:09 HeroicKatora

I think by default the ffmpeg encoder uses a global palette quantized for every color in the animation with a sierra2_4a dither.
https://ffmpeg.org/ffmpeg-filters.html#palettegen
https://ffmpeg.org/ffmpeg-filters.html#paletteuse

You can see an example generated palette with:

ffmpeg -i source.mp4 -vf palettegen pattern-palette.png

pattern-palette

ffmpeg straight to gif, 865kB.

ffmpeg -i source.mp4 pattern-ffmpeg.gif

Using the global palette but disabling dither, 1950kB.

ffmpeg -i source.mp4 -lavfi "palettegen[pal],[0:v][pal]paletteuse=new=1:dither=none" pattern-ffmpeg-global-palette-no-dither.gif

I think the following command is closest to what this crate does: generate a palette for every frame, 2779kB.

ffmpeg -i source.mp4 -lavfi "palettegen=stats_mode=single[pal],[0:v][pal]paletteuse=new=1:dither=none" pattern-ffmpeg-single-frame-no-dither.gif

With that context, 2.4MB isn't wildly out of place for what's being done.


I was curious about the gains that could be had from delta encoding with a naive approach by modifying the Frame::from_rgba_speed function. I was able to get the image-gif gifenc file down to 2019kB by re-encoding it, which is about a 17% file size decrease in this case. I would have used the source mp4 but I don't know how the frames were produced to make the original gif.

Delta-encoded gif

pattern-opt

The second frame

pattern-opt 002

So, more advanced delta encoding and global palette quanitzation to avoid the overhead of a palette for every frame would get much closer to the size of the ffmpeg gifs. The library supports global palettes but users have to generate it and map the pixels to the nearest color.

okaneco avatar Sep 07 '21 12:09 okaneco

I think this crate encodes fine. The difference you see is not from improved compression, but quite the opposite: it's because ffmpeg's naive remapping destroys so much of the color detail that it has less data left to encode.

If I use low quality setting in gifski (that uses this crate to encode) I get even smaller file (570KB): gifski

gifsicle-optimized LZW can take it down to 530KB.

kornelski avatar Oct 08 '22 01:10 kornelski