I maintain a plugin that depends on ruby-vips, and I'm having trouble with dependency managment #294
Replies: 12 comments 2 replies
-
Hi @rbuchberger, your project sounds very cool! libvips recently switched to libspng for PNG loading, perhaps this is causing problems? It tends to be stricter about PNG conformance and rejects some files which the older libpng accepted. On macos, libvips 8.10.6 (pushed to homebrew last night) tries to relax libspng rules a bit and should load most PNGs that libpng can load. I would try updating to that. More recent libpng versions are better with out of spec PNGs as well. On the Generally for deployment, I would use the platform libvips. Most linuxes have a reasonable version in their package managers, and building your own binary is annoying. If the platform libvips is too old, you must either build it yourself (argh), or deploy on something like docker. It's possible to build locally and then distribute the libvips binary as a tarball, but it would take a bit of skill to make it work well. |
Beta Was this translation helpful? Give feedback.
-
Here's libspng: It's a modern, clean, fast implementation of PNG load. libpng (the usual PNG load library) is hard to use, slow and has had almost endless security problems, so libspng ought to be step forward. |
Beta Was this translation helpful? Give feedback.
-
Thanks! I didn't know about the libspng difference, maybe that's what was breaking the netlify builds! I'll give that another shot. I'll also pass on the advice about updating the latest libvips version with homebrew. The magicksave error is because of my questionable error handling; whenever vips raises an error, we try again with magicksave: def write(image)
image.write_to_file(base.absolute_filename, **write_opts)
rescue Vips::Error
opts = write_opts.transform_keys do |key|
key == :Q ? :quality : key
end
image.magicksave(base.absolute_filename, **opts)
end I really should do a better job with that. Anyway, I was afraid that packaging it ourselves would be painful. I was under the impression that most linux distros ship reasonable versions of vips, that's half the reason I switched away from imagemagick! Thanks for the advice, I'll report back with results. |
Beta Was this translation helpful? Give feedback.
-
Ah OK. libvips already falls back to imagemagick if its own loaders fail. I would remove that step I think, unless you have a very specific reason to do it. On macos, you need to use homebrew ruby, I don't know if that could be part of the problem. Apple ship their own |
Beta Was this translation helpful? Give feedback.
-
libvips image loader detection works like this:
The imagemagick loader has the lowest priority and will run if everything else fails. There are two shortcuts for ICO and BMP to get magickload going quickly on them. You can see the class hierarchy with
So the CSV loader has no |
Beta Was this translation helpful? Give feedback.
-
That makes a lot of sense, thanks for the breakdown. Do savers work the same way? That blanket rescue is also suppressing the original error that's breaking other builds, which is spectacularly unhelpful, so I'll at minimum be adding an option to disable it. I'm feeling a bit foolish that I didn't think to take this step first; without the original error message it's obvoiusly going to be difficult to troubleshoot build failures. |
Beta Was this translation helpful? Give feedback.
-
Savers are just done on suffix: every subclass of VipsForeignSave has a priority and set of supported suffixes. The first saver with a matching suffix gets the job. There's nice jp2 load/save coming in 8.11, fwiw. |
Beta Was this translation helpful? Give feedback.
-
Ok, makes sense. I'll write an update to handle this better. Glad to hear jp2 is incoming! Will report back with results, might take a few days. |
Beta Was this translation helpful? Give feedback.
-
Update: I have the original error message!
Did the key for the quality setting for PNG files change recently? |
Beta Was this translation helpful? Give feedback.
-
I had a quick look at your plugin and it seems very nicely put together. I did notice one thing: you're not using For example, here's roughly what you're doing now: #!/usr/bin/ruby
require 'vips'
target_width = 200
image = Vips::Image.new_from_file ARGV[0]
scale_factor = target_width.to_f / image.width
image = image.resize scale_factor
image.write_to_file ARGV[1] I see:
That's the fastest of five runs: 190mb of memory and 0.24s of elapsed time.
I see:
60mb and 0.13s. It's faster because it can exploit tricks like shrink on load. jp2 is even more dramatic:
8x faster, half the memory use. |
Beta Was this translation helpful? Give feedback.
-
That's all really helpful, thanks so much! And thanks for the kind words, though I'm not sure how much I deserve them. I only barely know what I'm doing 😄 I'll definitely be switching over to use thumbnail; I had steered away from it because of the name and description. Simpler code and better performance makes it an easy decision. Regarding PNG quality, I'll just stop passing the option. Since it's breaking things for older versions of vips that we're often stuck with, and it's not all that useful to begin with, it's best to just forget about it. You've been amazingly helpful, I really appreciate the time you've spent to walk me through all this. I'll let you know what the performance improvement is from switching to thumbnail. |
Beta Was this translation helpful? Give feedback.
-
Weird, I'm seeing about a ~10% slowdown for the entire site build when using The plugin's most common workflow is to take a source image and generate 3-5 sizes of that image in 2-3 formats. Inputs are usually jpg or png, outputs are usually webp and whatever the input is. Looking at the rbspy flamegraph, most of that time is spent in Here's the def load_image
Vips::Image.thumbnail source.name, @base.width, **load_opts
end
def write(image)
case handler
when :vips
image.write_to_file(base.absolute_filename, **write_opts)
when :magick
image.magicksave(base.absolute_filename, **write_opts)
end
end
def load_opts
opts = { height: @base.height }
# PictureTag.keep returns an interestingness setting for smartcrop.
opts[:crop] = PictureTag.keep(@source.media_preset) if @source.crop?
opts
end
def write_opts
# Image specific write options, such as avif compression algorithm.
opts = PictureTag.preset['image_options'][@base.format] || {}
opts[:strip] = PictureTag.preset['strip_metadata']
# quality_key switches beween Q: and quality: as appropriate.
opts[quality_key] = base.quality unless %w[gif png].include? base.format
opts.transform_keys(&:to_sym)
end This is repeated for every output image. Is there something I can do better? To be completely clear, this is still 5 times faster than our old imagemagick based implementation. If there's an easy performance gain here I'm interested, but if not it's no sweat. |
Beta Was this translation helpful? Give feedback.
-
Hey all, firstly I really appreciate this project. I maintain jekyll_picture_tag, which is a jekyll plugin that takes the pain out of responsive images. I'm using vips via ruby-vips to handle all the image generation, and when it works it works really well. Build times are 1/5 of what they were when we were using imagemagick, and I really didn't have to write much code to interface with it. Thanks a ton for that!
The problems crop up when we try to deploy with it. Netlify builds fail on PNG images, and it looks like mac builds are completely broken. I know how to operate a package manager, but beyond that I'm not very smart on dependency management.
Netlify has beta support for homebrew, but
brew install libpng webp && build_site
didn't work, andbrew install vips && build_site
tried to compile about half an OS from source and ran over the build time limit.I don't even know where to start troubleshooting the MacOS issues, seeing as I don't have a mac to test on.
I'd like to offer guidance on how to compile & use a static binary with all dependencies included, but I don't have the slightest idea how to do that. Do y'all have any advice? Is that a good solution, or is there a better way to handle environment-agnostic deployment?
Beta Was this translation helpful? Give feedback.
All reactions