ruby-vips
| Language | Ruby (MRI 2.x/3.x, JRuby) |
|---|---|
| License | MIT (libvips: LGPL-2.1-or-later) |
| Repository | github.com/libvips/ruby-vips |
| Documentation | rubydoc.info/gems/ruby-vips, libvips jpegsave |
| Depends on | libvips 8.x (with libjpeg-turbo or mozjpeg), ruby-ffi |
| Read / write | yes / yes; progressive, subsampling, Huffman optimization, trellis quantization with mozjpeg; Exif, XMP, ICC |
libvips is a demand-driven image library: it processes images in small regions across threads instead of loading them whole, so a resize of a large JPEG uses a few tens of MB instead of hundreds. Vips::Image.thumbnail combines shrink-on-load (libjpeg’s ½, ¼, 1/8 scaled decoding), resizing and auto-rotation from Exif in one call, and is the fastest way to make thumbnails in Ruby.
libvips keeps metadata (Exif, XMP, ICC) when saving unless you ask it not to. It exposes every libjpeg option, and when built against mozjpeg it adds trellis quantization and scan optimization.
Installation#
# Debian / Ubuntu
sudo apt install libvips42
# macOS
brew install vips
gem install ruby-vips
On Windows, the gem pulls in the MSYS2 libvips package.
Basic usage: auto-rotate, resize and save a JPEG#
require "vips"
image = Vips::Image.new_from_file("photo.jpg")
puts "#{image.width} x #{image.height}, #{image.bands} bands"
image = image.autorot # apply the Exif orientation
image = image.resize(1200.0 / image.width)
image.jpegsave("out.jpg", Q: 85, optimize_coding: true)
new_from_file only reads the header; pixels are decoded when an operation needs them.
How to make a JPEG thumbnail quickly#
# 300 px wide, height in proportion; decodes at reduced scale and auto-rotates
thumb = Vips::Image.thumbnail("photo.jpg", 300)
thumb.write_to_file("thumb.jpg", Q: 80)
# exact 200x200 square, cropped around the center
square = Vips::Image.thumbnail("photo.jpg", 200, height: 200, crop: :centre)
square.write_to_file("square.jpg")
How to save a progressive JPEG without chroma subsampling#
image.jpegsave("out.jpg",
Q: 90,
interlace: true, # progressive
subsample_mode: :off, # 4:4:4
optimize_coding: true)
With a mozjpeg-based libvips you can add trellis_quant: true, optimize_scans: true, quant_table: 3 for smaller files.
How to read Exif data#
libvips exposes Exif tags as fields named exif-ifd0-*, exif-ifd2-* (Exif SubIFD) and exif-ifd3-* (GPS):
image = Vips::Image.new_from_file("photo.jpg")
puts image.get("exif-ifd0-Model") if image.get_typeof("exif-ifd0-Model") != 0
puts image.get("exif-ifd2-DateTimeOriginal") if image.get_typeof("exif-ifd2-DateTimeOriginal") != 0
puts image.get("orientation") if image.get_typeof("orientation") != 0
image.get_fields.grep(/^exif-/).each { |name| puts "#{name}: #{image.get(name)}" }
Values are strings like Canon EOS R6 (Canon EOS R6, ASCII, 13 components, 13 bytes): the converted value first, then the raw form.
How to strip metadata and encode to memory#
# keep: :none removes Exif, XMP, IPTC and ICC (libvips 8.15+); older versions use strip: true
data = image.jpegsave_buffer(Q: 80, keep: :none)
File.binwrite("clean.jpg", data)
from_memory = Vips::Image.new_from_buffer(data, "")
Tips#
- Open with
access: :sequential(Vips::Image.new_from_file(path, access: :sequential)) when you process top to bottom once; libvips then streams the JPEG and needs very little memory. Vips.cache_set_max(0)turns off the operation cache in long-running processes that handle many different files.- Use
Vips.block_untrusted(true)on servers to disable loaders that aren’t fuzzed (libvips 8.13+).