All formats

ruby-vips

LanguageRuby (MRI 2.x/3.x, JRuby)
LicenseMIT (libvips: LGPL-2.1-or-later)
Repositorygithub.com/libvips/ruby-vips
Documentationrubydoc.info/gems/ruby-vips, libvips gifsave
Depends onlibvips 8.12+ (GIF saving uses the bundled cgif and libimagequant or quantizr)
Read / writeyes / yes; animation, delays, loop count, transparency, palette size, dithering

libvips represents an animated image as a single tall image: frames are stacked top to bottom, each page-height pixels high. Metadata fields hold n-pages, delay (an array of milliseconds per frame) and loop (0 = forever). Because all frames are one image, any operation applies to the whole animation, and thumbnail knows to resize each page.

GIF loading composites frames onto full pages. Saving quantizes with libimagequant (or the compatible quantizr), reuses palettes between similar frames, and makes unchanged pixels transparent to shrink the file. It’s much faster than ImageMagick for resizing animated GIFs.

Installation#

Shell
# Debian / Ubuntu
sudo apt install libvips42
# macOS
brew install vips
gem install ruby-vips

Basic usage: load an animated GIF and read its metadata#

Ruby
require "vips"

# n: -1 loads all frames (by default only the first is loaded)
image = Vips::Image.new_from_file("anim.gif", n: -1)

page_height = image.get("page-height")
frames = image.height / page_height
delays = image.get_typeof("delay") != 0 ? image.get("delay") : []
loop_count = image.get_typeof("loop") != 0 ? image.get("loop") : 0

puts "#{image.width} x #{page_height}, #{frames} frames, loop #{loop_count}"
puts "delays (ms): #{delays.inspect}"

How to resize an animated GIF#

Ruby
# thumbnail resizes every frame; "[n=-1]" loads all frames
small = Vips::Image.thumbnail("anim.gif[n=-1]", 200)
small.gifsave("small.gif")

How to extract one frame as PNG#

Ruby
image = Vips::Image.new_from_file("anim.gif", n: -1)
h = image.get("page-height")
image.crop(0, 3 * h, image.width, h).write_to_file("frame3.png")   # 4th frame

# or load only that frame
Vips::Image.new_from_file("anim.gif", page: 3).write_to_file("frame3.png")

How to create an animated GIF from images#

Join the frames vertically and set the animation fields:

Ruby
require "vips"

frames = %w[f1.png f2.png f3.png].map { |f| Vips::Image.new_from_file(f) }
anim = Vips::Image.arrayjoin(frames, across: 1)
anim = anim.mutate do |m|
  m.set_type! GObject::GINT_TYPE, "page-height", frames.first.height
  m.set_type! Vips::ARRAY_INT_TYPE, "delay", [200] * frames.size   # ms per frame
  m.set_type! GObject::GINT_TYPE, "loop", 0                         # forever
end
anim.gifsave("anim.gif")

All frames must have the same width and height.

How to make a GIF smaller#

Ruby
image = Vips::Image.new_from_file("anim.gif", n: -1)
image.gifsave("smaller.gif",
  bitdepth: 6,                 # up to 63 colors (one entry is reserved for transparency)
  dither: 0.5,                 # 0 = none, 1 = full Floyd-Steinberg
  effort: 10,                  # slower, better palette
  interframe_maxerror: 8)      # treat near-identical pixels as unchanged

Tips#