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

Size of gif increases after processing with Vips::Image.thumbnail #244

Open
chloe-meister opened this issue Jul 22, 2020 · 13 comments
Open

Size of gif increases after processing with Vips::Image.thumbnail #244

chloe-meister opened this issue Jul 22, 2020 · 13 comments
Labels

Comments

@chloe-meister
Copy link

@chloe-meister chloe-meister commented Jul 22, 2020

Hi there!

I'm using Vips::Image.thumbnail to resize gif images and I noticed that for some gifs, the size after resizing is much bigger than before.

My code:

input_path = input_file.path << "[n=-1]"
image = Vips::Image.thumbnail(input_path, size, size: :down)
image.magicksave(temp_file.path, optimize_gif_frames: true, optimize_gif_transparency: true)

As an example, the gif attached is 1.9MB, and after processing by the code above it's 4.88MB.
giphy

I've tried changing the quality in magicksave:

image.magicksave(temp_file.path, optimize_gif_frames: true, optimize_gif_transparency: true, quality: x)

with x = 0, 1, 50 and 100 but it didn't make any difference.

I wonder what I could be missing?

Many thanks!
Chloe

@chloe-meister chloe-meister changed the title Size of gif increase after processing with Vips::Image.thumbnail Size of gif increases after processing with Vips::Image.thumbnail Jul 22, 2020
@jcupitt
Copy link
Member

@jcupitt jcupitt commented Jul 22, 2020

Hello @chloe-meister,

I think that's just the way it is. magicksave exposes some of imagemagick's GIF write controls, but not all, unfortunately.

GIF optimisation is a very manual process. If your source GIF has been carefully tuned, you'll need to do the same amount of tuning to get a small thumbnail from it.

@jcupitt jcupitt added the question label Jul 22, 2020
@jcupitt
Copy link
Member

@jcupitt jcupitt commented Jul 22, 2020

I'll have a quick look inside the GIF, it's an interesting test image.

@chloe-meister
Copy link
Author

@chloe-meister chloe-meister commented Jul 22, 2020

I understand. Thanks for the quick answer and for looking at the gif. Hopefully you can find something interesting out of it 🙂

@jcupitt
Copy link
Member

@jcupitt jcupitt commented Jul 22, 2020

Actually, it seems to be working OK for me. I tried:

$ vips copy wave.gif[n=-1] x.gif[optimize-gif-frames,optimize-gif-transparency]
$ ls -l wave.gif x.gif 
-rw-rw-r-- 1 john john 1916652 Jul 22 13:44 wave.gif
-rw-rw-r-- 1 john john 1919527 Jul 22 17:43 x.gif

So the size stays low. However, if I shrink I see:

$ vipsthumbnail wave.gif[n=-1] -o x.gif[optimize-gif-frames,optimize-gif-transparency] --size 400
$ ls -l x.gif
-rw-rw-r-- 1 john john 5106476 Jul 22 17:47 x.gif

What's happening is that the shrink operation also slightly sharpens. Your original GIF has perfect flat edges that compress well, but the raised edges on the shrunk version need more bytes to represent.

I don't think there's much that can be done about this, unfortunately.

@chloe-meister
Copy link
Author

@chloe-meister chloe-meister commented Jul 23, 2020

I also tried just loading and saving the image:

image = Vips::Image.new_from_file(input_file.path, n: -1)
image.magicksave(temp_file.path, optimize_gif_frames: true, optimize_gif_transparency: true)

and indeed the size stays low. As you pointed out, the issue is with shrinking. The increase in file size makes sense given that it's sharpening as well.

Thank you very much for the clarifications 🙂

@Nakilon
Copy link
Contributor

@Nakilon Nakilon commented Jul 23, 2020

Just wondering. Is it possible to make a thumbnail with another kernel? Such as "nearest" that won't mix colors.

@jcupitt
Copy link
Member

@jcupitt jcupitt commented Jul 23, 2020

Yes, thumbnail used to have a kernel, option, but we removed it as it was too confusing. People kept picking the wrong one and getting bad results :(

You can do the thumbnailing yourself since it's pretty easy with GIF. Something like:

#!/usr/bin/ruby

require 'vips'

image = Vips::Image.new_from_file ARGV[0], access: :sequential, n: -1

target_size = ARGV[2].to_i

# image.height will be for all frames -- we want the height of one page. If
# page-height is missing, default to image height
page_height = if image.get_typeof("page-height") != 0 
                image.get("page-height")
              else
                image.height
              end

# we now know the number of pages in the animation
n_loaded_pages = image.height / page_height

# resize to fit a inside target_size by target_size box
scale = [target_size.to_f / image.width, target_size.to_f / page_height].min

# adjust the scale so that we hit the image.height exactly, or we'll hae frames
# that vary in size
target_height = (page_height * scale).round
scale = (target_height.to_f * n_loaded_pages) / image.height

# normally you'd need to premultiply around resize, but :nearest does not mix
# pixels, so there's no need
image = image.resize scale, kernel: :nearest

# we need to set the new page_height
image = image.copy
image.set "page-height", target_height

image.write_to_file ARGV[1], 
  optimize_gif_frames: true, optimize_gif_transparency: true

Running:

$ ./thumb_nearest.rb ~/pics/wave.gif x.gif 400

ie. a 20% shrink makes:

x

1.34mb, so it's smaller. Nearest will give nasty artifacts with large reductions, of course, eg.:

$ ./thumb_nearest.rb ~/pics/wave.gif x.gif 64

You can see there are odd "sparkle" effects:

x

@chloe-meister
Copy link
Author

@chloe-meister chloe-meister commented Jul 23, 2020

Thank you so much, works a treat!

@Nakilon
Copy link
Contributor

@Nakilon Nakilon commented Jul 23, 2020

Hmmm, I don't understand the reason of sparkles. It's kind of divided on even and odd frames. Maybe it's related to this particular gif or encoder does something different on even/odd phases.

In theory shrinking 4x should be the same as shrinking twice by 2x.

@jcupitt
Copy link
Member

@jcupitt jcupitt commented Jul 23, 2020

A 8x nearest-neighbour shrink is just taking every 8th pixel, so you get horrible aliasing. You could use a triangle filter instead, but they look very soft, and the mushy edges would be hard to compress.

@chloe-meister
Copy link
Author

@chloe-meister chloe-meister commented Jul 23, 2020

If that can help, when I tried thumbnailing as above with large reductions I didn't get the sparkles but the last frames have got many white parts in them, as shown with the file attached.
avatar_image

My implementation is really close to @jcupitt 's above:

  def resize_gif(vips_image, size)
    # GH issue: https://github.com/libvips/ruby-vips/issues/244
    # This will get nasty with large reductions
    target_size = size.to_i

    page_height = vips_image.get("page-height") || vips_image.height

    n_frames = vips_image.height / page_height

    scale = [target_size.to_f / vips_image.width, target_size.to_f / page_height].min

    target_height = (page_height * scale).round
    scale = (target_height.to_f * n_frames) / vips_image.height

    # normally you'd need to premultiply around resize, but :nearest does not mix
    # pixels, so there's no need
    new_image = vips_image.resize(scale, kernel: :nearest)

    # we need to set the new page_height
    new_image.set("page-height", target_height)

    new_image
  end

Called by:

image = Vips::Image.new_from_file(input_file.path, n: -1, access: :sequential)
image = resize_gif(image, size)
image.write_to_file(temp_file.path, optimize_gif_frames: true, optimize_gif_transparency: true)
@jcupitt
Copy link
Member

@jcupitt jcupitt commented Jul 23, 2020

I was using 8.10 and it has some fixes to the GIF loader.

You'll need to add a copy before the set or you'll see errors in large programs. You must have a unique reference to an image before modifying the metadata.

@chloe-meister
Copy link
Author

@chloe-meister chloe-meister commented Jul 23, 2020

Thanks for clarifying, I wondered why the copy indeed. I'll add it back 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
3 participants
You can’t perform that action at this time.