Multipage and Animated Images
This page adapts the upstream libvips guide for ol.vips. The original
reference is
Multipage and animated images.
libvips represents animated and multipage images as one tall image plus
metadata describing how that strip should be interpreted. In ol.vips, the
main metadata fields are
v/pages,
v/page-height,
v/page-delays, and
v/loop-count.
This model works best when every page or frame has the same dimensions. That is common for animated GIF and WebP, and also for some TIFF or PDF inputs. If page sizes vary, read and process them one at a time instead of treating the file as one uniform strip.
Reading multipage images
By default, libvips usually reads only the first page or frame. Pass {:n -1}
to load every page, or use {:page … :n …} to load a single page or a
range.
(require ' [ol.vips :as v]
' [ol.vips.operations :as ops])
(with-open [image (ops/gifload "test/fixtures/cogs.gif" {:n -1 })]
(select-keys (v/metadata image)
[:width :height :pages :page-height :loop :delay ]))
;; => {:width 85
;; :height 385
;; :pages 5
;; :page-height 77
;; :loop 32761
;; :delay [0 50 50 50 50]}
Points to note:
-
ops/gifloadwith{:n -1}loads every frame in the animation. -
:page-heightis the height of one frame within the strip. -
:pages * :page-height == :heightfor uniform-height images. -
:loopis the loop count for animated formats. -
:delayis a vector of frame delays in milliseconds.
You can also load a subset of frames:
(with-open [two-frames (ops/gifload "test/fixtures/cogs.gif" {:page 2 :n 2 })]
(select-keys (v/metadata two-frames) [:height :pages :page-height ]))
Be careful interpreting metadata on partial loads. The loaded strip height
reflects the selected range, but some loaders can still preserve source
metadata such as the original total n-pages. For frame slicing logic, rely on
the loaded image dimensions together with page-height, not only on the
reported source page count.
The same loader options apply to other multipage-capable loaders such as
ops/webpload and
ops/tiffload, or to the generic
v/from-file path when you want format
autodetection.
For PDF input, remember that ol.vips blocks libvips operations marked as
untrusted during initialization. If you have a trusted PDF input path and need
to load it, opt in first with
v/set-block-untrusted-operations!
set to false.
Writing multipage images
If an image already has the right strip layout and metadata, writing an animated or multipage image is just a normal save. For example, you can load a GIF animation and write it back as animated WebP:
(with-open [image (ops/gifload "test/fixtures/cogs.gif" {:n -1 })]
(v/write-to-file image "cogs.webp" ))
That works because the loaded image already carries the n-pages,
page-height, loop, and delay metadata that the saver needs.
The same pattern applies to multipage TIFF output with
ops/tiffsave or
to generic saves through v/write-to-file
when libvips can infer the target format from the filename.
Building an animation from frames
The upstream libvips examples build a vertical strip and then set metadata by
hand. In ol.vips, prefer
v/assemble-pages when you already have
a sequence of equal-sized frames. It joins the frames into one strip and sets
the page metadata for you.
(require ' [ol.vips :as v]
' [ol.vips.operations :as ops])
(with-open [base (v/from-file "test/fixtures/puppies.jpg" )
frame-a (ops/extract-area base 0 0 40 30 )
frame-b (ops/extract-area base 10 10 40 30 )
frame-c (ops/extract-area base 20 20 40 30 )
animated (v/assemble-pages [frame-a frame-b frame-c]
{:loop 2
:delay [80 120 160 ]})]
(v/write-to-file animated "puppies.gif" )
(select-keys (v/metadata animated)
[:width :height :pages :page-height :loop :delay ]))
Here, frame-a, frame-b, and frame-c are just ordinary image handles.
v/assemble-pages does not require a special frame type. The frames
argument is simply a non-empty collection of same-sized images, with each image
becoming one animation frame in the output.
In practice, those images can come from any image-producing operation, not only
from ops/extract-area. You
can resize, crop, composite, render text, or load separate files first, then
pass the resulting image handles to v/assemble-pages.
If you already have a tall strip and need to annotate or adjust it, use
v/assoc-pages,
v/assoc-page-height,
v/assoc-page-delays, and
v/assoc-loop-count.
Applying operations page by page
For uniform-height multipage images, the page-aware helpers apply ordinary operations frame by frame and keep the metadata consistent.
(with-open [image (ops/gifload "test/fixtures/cogs.gif" {:n -1 })
cropped (v/extract-area-pages image 10 7 50 50 )
turned (v/rot-pages cropped :d90 )
looped (v/assoc-loop-count turned 2 )]
(v/write-to-file looped "cogs-turned.gif" )
(select-keys (v/metadata looped)
[:width :height :pages :page-height :loop :delay ]))
The main helpers are:
For single-page inputs, these helpers fall back to the ordinary single-image behavior. For true multipage work, they assume that the image can be divided cleanly into equal-height pages.