Skip to content

cgifsave: palette change POC #2824

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

Merged
merged 2 commits into from
Jun 8, 2022

Conversation

DarthSim
Copy link
Contributor

Hey!

This is the POC of my approach to generating palettes for cgifsave. How it works:

  1. We compute a palette for each frame. The first frame's palette we use as a global one.
  2. For each frame except the first we compare the palette computed in step 1 with the global palette and the local palette of the previous frame (if any).
  3. If there's a local palette in the previous frame, and the diff between it and the current frame palette is small enough, we use the palette from the previous frame as local for the current frame.
  4. Otherwise, if the diff between the global palette and the current frame palette is small enough, we don't use a local palette.
  5. Otherwise, we use the palette computed in step 1 as a local palette for the current frame.

With this approach, we mitigate the difference between neighbor frame colors which helps transparency and frame optimizers do their jobs.

Some numbers:

Master speeed:

$ /usr/bin/time -f %M:%e vips copy /images/animated_gif12.gif[n=-1] /images/animated_gif12_vips_out_master.gif[reoptimise=1]
40344:5.21

$ /usr/bin/time -f %M:%e vips copy /images/animated_gif13.gif[n=-1] /images/animated_gif13_vips_out_master.gif[reoptimise=1]
32552:5.33

$ /usr/bin/time -f %M:%e vips copy /images/animated_gif15.gif[n=-1] /images/animated_gif15_vips_out_master.gif[reoptimise=1]
27096:3.36

$ /usr/bin/time -f %M:%e vips copy /images/animated_gif16.gif[n=-1] /images/animated_gif16_vips_out_master.gif[reoptimise=1]
41744:2.18

New speeed:

$ /usr/bin/time -f %M:%e vips copy /images/animated_gif12.gif[n=-1] /images/animated_gif12_vips_out_new.gif[reoptimise=1]
40340:4.98

$ /usr/bin/time -f %M:%e vips copy /images/animated_gif13.gif[n=-1] /images/animated_gif13_vips_out_new.gif[reoptimise=1]
32972:5.11

$ /usr/bin/time -f %M:%e vips copy /images/animated_gif15.gif[n=-1] /images/animated_gif15_vips_out_new.gif[reoptimise=1]
26884:3.28

$ /usr/bin/time -f %M:%e vips copy /images/animated_gif16.gif[n=-1] /images/animated_gif16_vips_out_new.gif[reoptimise=1]
41592:2.06

Result sizes

Here I resized the images a bit to increase the color number.

$ ls -l /images/animated_gif*_vips_out_*
-rw-r--r-- 1 *** ***   687545 May 27 06:34 /images/animated_gif12_vips_out_master.gif
-rw-r--r-- 1 *** ***   631936 May 27 06:50 /images/animated_gif12_vips_out_new.gif
-rw-r--r-- 1 *** ***  1051173 May 27 06:34 /images/animated_gif13_vips_out_master.gif
-rw-r--r-- 1 *** ***   997631 May 27 06:50 /images/animated_gif13_vips_out_new.gif
-rw-r--r-- 1 *** ***  3355198 May 27 06:51 /images/animated_gif15_vips_out_master.gif
-rw-r--r-- 1 *** ***  3320212 May 27 06:50 /images/animated_gif15_vips_out_new.gif
-rw-r--r-- 1 *** *** 14747250 May 27 06:35 /images/animated_gif16_vips_out_master.gif
-rw-r--r-- 1 *** *** 14557252 May 27 06:51 /images/animated_gif16_vips_out_new.gif

Findings

As we see, the memory footprint is more or less the same for both approaches, yet my approach is slightly faster and produces slightly smaller files. Also, this approach doesn't have a frame size limitation.

@DarthSim DarthSim force-pushed the feature/gif-palette-change-poc branch 2 times, most recently from e1bb183 to 712f057 Compare May 27, 2022 13:26
@jcupitt
Copy link
Member

jcupitt commented May 27, 2022

Oh, nice! It seems to work well on the test images I tried. @dloebl, any thoughts?

@DarthSim DarthSim force-pushed the feature/gif-palette-change-poc branch 2 times, most recently from 26929fa to 420bcd4 Compare May 27, 2022 14:00
@DarthSim DarthSim marked this pull request as ready for review May 27, 2022 14:39
@DarthSim DarthSim force-pushed the feature/gif-palette-change-poc branch 2 times, most recently from 0ea45c8 to e97850e Compare May 27, 2022 17:11
@DarthSim DarthSim force-pushed the feature/gif-palette-change-poc branch from e97850e to c7a24b8 Compare May 30, 2022 11:43

best_dist = VIPS_MIN(best_dist, dist);

/* We fond the closest entry
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: fond -> found

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fixed

if( cgif->frame_checksum == 0 ||
checksum != cgif->frame_checksum ) {
cgif->frame_checksum = checksum;
static double pal_change_threshold = 64.0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method seems to be similar to the way the maxerror parameter is handled right now: cgifsave.c#L177
We could make the threshold dynamic (e.g. use maxerror here) and set maxerror to a value > 0 by default.

Copy link
Contributor Author

@DarthSim DarthSim May 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think maxerror fits well here. For example, 64 is a good value for the inter-palette error while it is too large for the inter-frame error. Yet I agree that this number should be dynamic. Maybe we should rename maxerror to, for example, interframe_maxerror and add a new param for inter-palette max error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made it dynamic and renamed maxerror. If everything is ok, I'll squash the commits. /cc @jcupitt

@dloebl
Copy link
Contributor

dloebl commented May 30, 2022

Looks like a nice improvement to me 👍
I've added some comments inline.

@DarthSim DarthSim force-pushed the feature/gif-palette-change-poc branch 2 times, most recently from 5bb685e to a819f16 Compare May 30, 2022 13:52
@jcupitt
Copy link
Member

jcupitt commented Jun 1, 2022

I'm seeing some errors with video clips -- it's tagging pixels as transparent when they should all stay solid. Perhaps dispose isn't always being set correctly?

@DarthSim
Copy link
Contributor Author

DarthSim commented Jun 4, 2022

Could you give a sample, please? I have like 16 test GIFs, and all of them work fine.

@jcupitt
Copy link
Member

jcupitt commented Jun 4, 2022

Sorry, it's not a GIF I can share :( I'm trying to find another example, but no luck so far.

Let's fix this up and merge it, then try to resolve any fails in another PR.

@jcupitt
Copy link
Member

jcupitt commented Jun 6, 2022

Hi, shall I fix the merge conflict?

@jcupitt
Copy link
Member

jcupitt commented Jun 6, 2022

I made a GIF I can share that fails.

This terrible GIF:

http://www.rollthepotato.net/~john/skate.gif

Converted using this branch:

$ vips copy skate.gif[n=-1] x.gif

Becomes:

http://www.rollthepotato.net/~john/x.gif

It looks like every pixel which is equal to the previous pixel becomes transparent in the output, which causes a lot of artefacts at the start of the clip.

@DarthSim DarthSim force-pushed the feature/gif-palette-change-poc branch from a819f16 to 62000a9 Compare June 7, 2022 07:07
@DarthSim
Copy link
Contributor Author

DarthSim commented Jun 7, 2022

Rebased and squashed the branch.

I made a GIF I can share that fails.

Oh, thank you! I'll dig it.

@DarthSim
Copy link
Contributor Author

DarthSim commented Jun 7, 2022

I've tested vips copy built from this branch, and I didn't get any artifacts: http://img.darthsim.me/ufHVruZ3o2.gif. I've tested it with both Quantizr and LIQ from the Debian package.

@jcupitt could you test it again on your end? Maybe it was fixed after the rebase?

@jcupitt
Copy link
Member

jcupitt commented Jun 7, 2022

I wiped everything and rebuilt, and I see still bad artefacts.

Could it be the cgif version? I'm using git master.

@dloebl
Copy link
Contributor

dloebl commented Jun 7, 2022

Maybe it's similar to the issue mentioned here?
I can reproduce the artefact with libimagequant 2.17 - but only if dithering is enabled. With Quantizr/libimagequant 4.0 the issue is gone on my side.

@DarthSim
Copy link
Contributor Author

DarthSim commented Jun 7, 2022

@dloebl That's it, thank you! I'll try to fix it.

@DarthSim
Copy link
Contributor Author

DarthSim commented Jun 7, 2022

Also I noticed that these pixels are not transparent but just grey. And, as Daniel said, artifacts appear only when dithering is enabled. So I believe it's a bug in LIQ 2.17 itself, and we can do nothing with it.

@jcupitt
Copy link
Member

jcupitt commented Jun 7, 2022

Ah good spot.

But these artefacts only appear with this branch -- master seems to work fine. I think something in this PR has triggered this behaviour.

@DarthSim
Copy link
Contributor Author

DarthSim commented Jun 7, 2022

But these artefacts only appear with this branch -- master seems to work fine. I think something in this PR has triggered this behaviour.

Master uses local palette for basically every frame while this branch doesn't use a local palette when reusing a global one. I noticed that LIQ 2.17 changes palette a lot during remapping, which is not good for us. AFAIK the only way to bypass this is using the histogram API, which is basically what liq_image_quantize does. But fork by @lovell doesn't have such API :(

@DarthSim
Copy link
Contributor Author

DarthSim commented Jun 7, 2022

BTW it seems like the same problem may occur when we reuse the global palette from the source image, too.

@jcupitt
Copy link
Member

jcupitt commented Jun 8, 2022

Perhaps we should merge, and then try to find a workaround for this in another PR. What do you think?

@DarthSim
Copy link
Contributor Author

DarthSim commented Jun 8, 2022

It seems like I fixed it! I added the vips__quantise_image_quantize_fixed method that does the following:

  • Quantizes the image and gets the palette.
  • Creates a fake 1-pixel image and adds all the colors from the palette to it as fixed colors. Fixed colors won't be changed during the remapping.
  • Quantizes the fake image and returns the result.

Somehow code with this hack works even faster than without it. Probably because LIQ doesn't change the palette.

@jcupitt
Copy link
Member

jcupitt commented Jun 8, 2022

That's great! All my tests pass too, well done.

I see some small leaks in valgrind, but maybe that's just libimagequant. Let's merge and maybe try to patch them in another PR.

@jcupitt jcupitt merged commit 19bef95 into libvips:master Jun 8, 2022
@jcupitt
Copy link
Member

jcupitt commented Jun 8, 2022

I did a quick benchmark against imagemagick:

$ /usr/bin/time -f %M:%e vipsthumbnail 3198.gif[n=-1] -o vips-13.gif --size 224
57344:0.62
$ /usr/bin/time -f %M:%e convert 3198.gif -resize 75% im.gif
200796:6.44
$ ls -l im.gif vips-13.gif 
-rw-rw-r-- 1 john john 3176859 Jun  8 16:26 im.gif
-rw-r--r-- 1 john john 2487189 Jun  8 16:25 vips-13.gif

So for this GIF at least (a short video clip) libvips is now 10x faster, needs 4x less memory, and makes GIFs which are 20% smaller!

Nice work everyone.

@jcupitt
Copy link
Member

jcupitt commented Jun 8, 2022

.... and the dithering of the libvips one is better too, so they look nicer.

@jcupitt
Copy link
Member

jcupitt commented Jun 8, 2022

Against libvips 8.12:

$ /usr/bin/time -f %M:%e vipsthumbnail 3198.gif[n=-1] -o vips-12.gif --size 224
57764:3.96
$ ls -l vips-12.gif
-rw-r--r-- 1 john john 3441032 Jun  8 16:34 vips-12.gif

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

Successfully merging this pull request may close these issues.

4 participants