# ol.vips

Load, transform, and write images with libvips from Clojure.

`ol.vips` exposes the runtime, I/O, metadata, and animated-image helpers that
most applications use directly. Loaders such as [`from-file`](#from-file),
[`from-buffer`](#from-buffer), and [`from-stream`](#from-stream) return closeable image handles.
Operations return new image handles rather than mutating the original image,
and sinks such as [`write-to-file`](#write-to-file), [`write-to-buffer`](#write-to-buffer), and
[`write-to-stream`](#write-to-stream) trigger evaluation of the underlying libvips pipeline.

Guides and API reference live at https://docs.outskirtslabs.com/ol.vips/next/.

Use this namespace for:

* runtime initialization and safety controls
* file, buffer, and stream input/output
* metadata and raw header access
* animated-image metadata and frame-aware helpers
* low-level generic operation calls with [`call`](#call)

## Related Namespaces

* [`ol.vips.operations`](api/ol-vips-operations.adoc) for generated wrappers around the libvips operation surface
* [`ol.vips.enums`](api/ol-vips-enums.adoc) for normalized enum keywords and enum value sets

## Example

```clojure
(require '[ol.vips :as v]
         '[ol.vips.operations :as ops])

(v/init!)

(with-open [thumb   (ops/thumbnail "dev/rabbit.jpg" 300 {:auto-rotate true})
            rotated (ops/rotate thumb 90.0)]
  (v/write-to-file rotated "thumbnail.jpg")
  (v/metadata rotated))
;; => {:width 300, :height 242, :bands 3, :has-alpha? false}
```

## init!

```clojure
(init!)
```

Starts libvips and loads the `ol.vips` native bindings for this process.

This function is idempotent. Most public API calls initialize libvips
lazily, so call `init!` when you want eager startup or want to inspect the
shared runtime state up front.

On first initialization, `ol.vips` restores the secure default and blocks
libvips operations marked as untrusted. See
[`set-block-untrusted-operations!`](#set-block-untrusted-operations!).

Returns the shared runtime state map, which should be considered an opaque handle and is not public API.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L47-L60)

---

## set-block-untrusted-operations!

```clojure
(set-block-untrusted-operations! blocked?)
```

Set whether libvips operations tagged as untrusted are blocked at runtime.

This is the direct wrapper around libvips `vips_block_untrusted_set`.
Pass `blocked?` as `true` to restore the secure default and block operations
libvips has marked as untrusted. Pass `false` to allow them to run.

Returns the current runtime state map.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L62-L71)

---

## set-operation-block!

```clojure
(set-operation-block! name blocked?)
```

Set the block state for a libvips operation class hierarchy.

`name` should be a libvips operation class name such as `"VipsForeignLoad"`
or `"VipsForeignLoadJpeg"`. libvips applies the block state at that point in
the class hierarchy and to all descendants. This is the direct wrapper around
libvips `vips_operation_block_set`.

Pass `blocked?` as `true` to block that class hierarchy, or `false` to allow
it.

Example:

```clojure
(v/set-operation-block! "VipsForeignLoad" true)
(v/set-operation-block! "VipsForeignLoadJpeg" false)
```

That blocks all loaders except the JPEG loader family.

Returns `{:name <string> :blocked? <boolean>}`.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L73-L95)

---

## operation-cache-settings

```clojure
(operation-cache-settings)
```

Returns the current libvips operation cache settings.

Because libvips operations are free of side effects, libvips can cache a
previous call to the same operation with the same arguments and return the
previous result again.

Returns `{:max <int> :size <int> :max-mem <bytes> :max-files <int>}`.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L97-L106)

---

## set-operation-cache-max!

```clojure
(set-operation-cache-max! max)
```

Sets the maximum number of operations libvips keeps in the operation cache.

Reducing this limit may trim cached operations immediately.

Returns the current cache settings map. See [`operation-cache-settings`](#operation-cache-settings).

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L108-L115)

---

## set-operation-cache-max-mem!

```clojure
(set-operation-cache-max-mem! max-mem)
```

Sets the maximum amount of tracked memory, in bytes, libvips allows before
it starts dropping cached operations.

libvips only tracks memory it allocates itself. Memory allocated by external
libraries is not included.

Returns the current cache settings map. See [`operation-cache-settings`](#operation-cache-settings).

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L117-L126)

---

## set-operation-cache-max-files!

```clojure
(set-operation-cache-max-files! max-files)
```

Sets the maximum number of tracked files libvips allows before it starts
dropping cached operations.

libvips only tracks file descriptors it opens itself. Descriptors opened by
external libraries are not included.

Returns the current cache settings map. See [`operation-cache-settings`](#operation-cache-settings).

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L128-L137)

---

## disable-operation-cache!

```clojure
(disable-operation-cache!)
```

Disables the libvips operation cache by setting the maximum cached
operation count to `0`.

This is often useful for image-proxy style workloads that process many
different images.

Returns the current cache settings map. See [`set-operation-cache-max!`](#set-operation-cache-max!).

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L139-L148)

---

## tracked-resources

```clojure
(tracked-resources)
```

Returns the current libvips tracked resource counters.

libvips uses these counters to decide when to start dropping cached
operations. The returned map has these keys:

|     |     |
| --- | --- |
| key | description |
| `:mem` | Bytes currently allocated via libvips tracked allocators |
| `:mem-highwater` | Largest tracked allocation total seen so far |
| `:allocs` | Number of active tracked allocations |
| `:files` | Number of tracked open files |

These counters only include resources libvips tracks itself.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L150-L165)

---

## operations

```clojure
(operations)
```

Returns the sorted libvips operation nicknames known to the runtime.

These are the names accepted by [`call`](#call) and by [`operation-info`](#operation-info), such as
`"flip"`, `"rotate"`, or `"thumbnail_image"`.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L167-L173)

---

## operation-info

```clojure
(operation-info operation-name)
```

Describes a libvips operation by nickname.

Returns a map with the operation `:name`, a short `:description`, and an
`:args` vector describing each argument’s name, blurb, type, and whether it
is an input, output, or required argument.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L175-L182)

---

## encode-enum

```clojure
(encode-enum enum-type-name value)
```

Encodes a Clojure enum value for a libvips enum type.

`enum-type-name` should be a libvips GType name such as `"VipsDirection"`.
Pass a keyword like `:horizontal` to get the corresponding integer value.
Integer values pass through unchanged.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L184-L191)

---

## decode-enum

```clojure
(decode-enum enum-type-name value)
```

Decodes a libvips enum integer to a normalized Clojure keyword.

`enum-type-name` should be a libvips GType name such as `"VipsDirection"`.

```clojure
(v/decode-enum "VipsDirection"
               (v/encode-enum "VipsDirection" :horizontal))
;;=> :horizontal
```

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L193-L204)

---

## call

```clojure
(call operation-name opts)
```

Calls a libvips operation by nickname.

This is the low-level generic binding API described by libvips for language
bindings: create an operation from its nickname, set properties, execute with
the operation cache, then extract outputs.

`operation-name` should be a libvips operation nickname such as `"flip"` or
`"embed"`. `opts` keys should match libvips argument names as keywords.
Enum arguments accept the normalized keywords used by [`encode-enum`](#encode-enum). Image
arguments accept image handles and prior operation result maps with `:out`.

Returns the operation outputs in the most useful shape for Clojure:

* If the only output is an image at `:out`, returns that image handle.
* Otherwise returns a map of outputs.
* Returned output maps that hold closeable image values are themselves
  closeable.

Example:

```clojure
(v/call "flip" {:in image
                  :direction :horizontal})
```

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L206-L232)

---

## from-file

```clojure
(from-file path)
(from-file path opts)
```

Opens an image from `path` and returns a closeable image handle.

`path` can be a string, `java.nio.file.Path`, or anything else coercible to a
path string. Arity 2 appends `opts` in libvips option-string form. The
available option keys depend on the loader libvips selects for that path.

To discover loader-specific options like `:shrink`, see the generated
wrappers in [`ol.vips.operations`](api/ol-vips-operations.adoc), for example
[`ol.vips.operations/jpegload`](api/ol-vips-operations.adoc#jpegload) or [`ol.vips.operations/pngload`](api/ol-vips-operations.adoc#pngload). For enum
option values like `:sequential` on `:access`, see [`ol.vips.enums/access`](api/ol-vips-enums.adoc#access)
or [`ol.vips.enums/describe`](api/ol-vips-enums.adoc#describe).

```clojure
(v/from-file "input.jpg" {:access :sequential
                            :shrink 2})
```

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L234-L254)

---

## write-to-file

```clojure
(write-to-file image sink)
(write-to-file image sink opts)
```

Writes `image` to `sink` and returns `image`.

libvips infers the saver from the sink path or extension. Arity 3 appends
`opts` in libvips option-string form. The available option keys depend on
the saver libvips selects for that sink.

To discover saver-specific options, see the generated wrappers in
[`ol.vips.operations`](api/ol-vips-operations.adoc), for example [`ol.vips.operations/pngsave`](api/ol-vips-operations.adoc#pngsave) or
[`ol.vips.operations/jpegsave`](api/ol-vips-operations.adoc#jpegsave). For enum option values, see
[`ol.vips.enums`](api/ol-vips-enums.adoc).

```clojure
(v/write-to-file image "output.png" {:compression 9})
```

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L256-L274)

---

## from-buffer

```clojure
(from-buffer source)
(from-buffer source opts)
```

Opens an image from in-memory bytes and returns a closeable image handle.

`source` may be a byte array or a byte sequence. Arity 2 passes `opts` as a
libvips option string for the loader.

The available option keys depend on the loader that recognizes the input
bytes. See [`ol.vips.operations`](api/ol-vips-operations.adoc) for the generated loader wrappers and
[`ol.vips.enums`](api/ol-vips-enums.adoc) for enum value sets.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L276-L288)

---

## from-stream

```clojure
(from-stream is)
(from-stream is opts)
```

Opens an image from an `InputStream` and returns a closeable image handle.

The stream is bridged to a libvips source and is closed when the returned
image handle is closed. Arity 2 passes `opts` as a libvips option string for
the loader.

This is useful for streaming or non-file inputs, especially together with
loader hints like `:access :sequential`.

The available option keys depend on the loader that recognizes the stream.
See [`ol.vips.operations`](api/ol-vips-operations.adoc) for the generated loader wrappers and
[`ol.vips.enums/access`](api/ol-vips-enums.adoc#access) for valid `:access` values.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L290-L306)

---

## write-to-buffer

```clojure
(write-to-buffer image suffix)
(write-to-buffer image suffix opts)
```

Encodes `image` and returns the result as a byte array.

`suffix` selects the saver and can include libvips save options directly,
such as `".png"` or `".png[compression=9]"`. Arity 3 appends `opts` to
`suffix` in libvips option-string form.

To discover saver-specific options, see the generated wrappers in
[`ol.vips.operations`](api/ol-vips-operations.adoc), for example [`ol.vips.operations/pngsave`](api/ol-vips-operations.adoc#pngsave) or
[`ol.vips.operations/jpegsave`](api/ol-vips-operations.adoc#jpegsave).

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L308-L321)

---

## write-to-stream

```clojure
(write-to-stream image os suffix)
(write-to-stream image os suffix opts)
```

Encodes `image` to an `OutputStream` and returns `image`.

`suffix` selects the saver, for example `".png"` or `".jpg"`. Arity 4
appends `opts` to `suffix` in libvips option-string form.

The stream is flushed and closed after the write completes, and is also
closed if the write fails.

To discover saver-specific options, see the generated wrappers in
[`ol.vips.operations`](api/ol-vips-operations.adoc), for example [`ol.vips.operations/pngsave`](api/ol-vips-operations.adoc#pngsave) or
[`ol.vips.operations/jpegsave`](api/ol-vips-operations.adoc#jpegsave).

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L323-L338)

---

## metadata

```clojure
(metadata image)
```

Returns a curated metadata map for `image`.

This is the high-level metadata view for the public API. It includes the core
image header fields such as width, height, bands, format, coding,
interpretation, resolution, offsets, and `:has-alpha?`, plus common animated
image fields when present such as `:pages`, `:page-height`, `:loop`, and
`:delay`.

For raw header and metadata access by libvips field name, see [`field`](#field),
[`field-names`](#field-names), and [`headers`](#headers).

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L340-L352)

---

## field

```clojure
(field image field-name)
(field image field-name not-found)
```

Returns the libvips header or metadata field named by `field-name`.

Items of metadata are identified by strings. Use this for direct access to
libvips header fields and attached metadata such as `"xres"`,
`"icc-profile-data"`, or `"delay"`.

Arity 2 returns `nil` for missing fields. Arity 3 returns `not-found`
instead.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L354-L366)

---

## field-as-string

```clojure
(field-as-string image field-name)
(field-as-string image field-name not-found)
```

Returns the libvips field named by `field-name` rendered as a string.

This is the direct string form of a header or metadata field, useful for
display and debugging. Arity 3 returns `not-found` when the field is
absent.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L368-L377)

---

## field-names

```clojure
(field-names image)
```

Returns the libvips field names attached to `image`.

This includes both core header fields and attached metadata field names.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L379-L384)

---

## headers

```clojure
(headers image)
```

Returns the raw libvips header and metadata map for `image`.

Keys are the original libvips field names as strings. Prefer [`metadata`](#metadata)
when you want the higher-level normalized public view.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L386-L392)

---

## has-field?

```clojure
(has-field? image field-name)
```

Returns `true` if `image` has the libvips field named by `field-name`.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L394-L397)

---

## width

```clojure
(width image)
```

Returns the image width in pixels.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L399-L402)

---

## height

```clojure
(height image)
```

Returns the image height in pixels.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L404-L407)

---

## bands

```clojure
(bands image)
```

Returns the number of image bands as an integer.

libvips images have three dimensions: width, height, and bands.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L409-L414)

---

## has-alpha?

```clojure
(has-alpha? image)
```

Returns `true` if `image` has an alpha band.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L416-L419)

---

## shape

```clojure
(shape image)
```

Returns `[width height bands]` for `image`.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L421-L426)

---

## copy-memory

```clojure
(copy-memory image)
```

Materializes `image` into a private in-memory `VipsImage` and returns it as a
normal closeable image handle.

This is effectively a sink. Calling `copy-memory` forces libvips to evaluate
the current pipeline now, render the pixels into memory, and wrap that memory
in a new image handle. Unlike [`write-to-file`](#write-to-file), [`write-to-buffer`](#write-to-buffer), or
[`write-to-stream`](#write-to-stream), the result is still an image you can keep using in
downstream image operations.

This is useful when an intermediate image will be reused several times and you
do not want libvips to recompute the upstream pipeline for each branch. It is
also the explicit way to ask for a private memory-backed image before applying
mutating draw operations.

Behavior:

* Preserves the rendered image pixels and image metadata.
* Returns a new handle that remains usable after the source pipeline handles
  have been closed.
* May avoid an extra copy if libvips determines the input is already a simple
  readable memory image, in which case it can return another reference to the
  existing image instead of allocating again.
* Trades CPU savings for higher memory use, so it is best reserved for
  intermediates you know are reused often enough to justify retaining the
  pixels.

Example:

```clojure
(with-open [base   (v/from-file "input.jpg")
            step   (-> base
                       (ops/resize 0.5)
                       (ops/sharpen))
            cached (v/copy-memory step)]
  (do-something cached)
  (do-something-else cached))
```

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L428-L467)

---

## assoc-field

```clojure
(assoc-field image field-name value)
(assoc-field image field-name value opts)
```

Returns a new image with the libvips field named by `field-name` set to `value`.

`assoc-field` is immutable at the API level: it leaves `image` unchanged and
returns a new image handle with the updated header or metadata field.

Arity 4 accepts an options map.

Options:

|     |     |
| --- | --- |
| key | description |
| `:type` | Explicit libvips field type to use when `value` is ambiguous, such as `:array-int` for integer vectors |

Header fields such as `"xres"` and `"yres"` are updated as real libvips
header values so they survive save and reload. Custom metadata fields are
copied onto a fresh image header and can be read back with [`field`](#field) or
[`headers`](#headers).

Example:

```clojure
(with-open [image  (v/from-file "input.jpg")
            tagged (-> image
                       (v/assoc-field "xres" 10.0)
                       (v/assoc-field "delay" [10 20 30] {:type :array-int}))]
  [(v/field tagged "xres")
   (v/field tagged "delay")])
```

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L469-L501)

---

## update-field

```clojure
(update-field image field-name f & args)
```

Returns a new image with `field-name` updated by applying `f` to its current value.

This is the functional update variant of [`assoc-field`](#assoc-field). The current field
value is read with [`field`](#field) and passed to `f` along with any extra `args`.
The result becomes the new field value on the returned image.

Example:

```clojure
(with-open [image   (v/from-file "input.jpg")
            tagged  (v/assoc-field image "custom-int" 42)
            updated (v/update-field tagged "custom-int" inc)]
  (v/field updated "custom-int"))
;; => 43
```

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L503-L520)

---

## dissoc-field

```clojure
(dissoc-field image field-name)
```

Returns a new image with the libvips field named by `field-name` removed.

This leaves `image` unchanged and removes the field from a copied image
header. It is most useful for stripping attached metadata fields that should
not be preserved downstream.

Example:

```clojure
(with-open [image    (v/from-file "input.jpg")
            tagged   (v/assoc-field image "custom-string" "hello")
            stripped (v/dissoc-field tagged "custom-string")]
  (v/field stripped "custom-string"))
;; => nil
```

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L522-L539)

---

## pages

```clojure
(pages image)
```

Returns the animated page count from `image`, or `nil` when it is absent.

This reads the libvips `"n-pages"` field directly. For ordinary single-page
images that field is usually missing, so `pages` commonly returns `nil`.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L541-L547)

---

## page-height

```clojure
(page-height image)
```

Returns the per-frame height for an animated image, or `nil` when it is absent.

libvips stores animated images as frames stacked vertically in one image.
`page-height` is the height of each logical frame.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L549-L555)

---

## page-delays

```clojure
(page-delays image)
```

Returns the animated frame delay vector from `image`, or `nil` when it is absent.

Delay values are returned as integers in the same order as the frames.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L557-L562)

---

## loop-count

```clojure
(loop-count image)
```

Returns the animated loop count from `image`, or `nil` when it is absent.

A loop count of `0` means loop forever when the target format supports that
convention.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L564-L570)

---

## assoc-pages

```clojure
(assoc-pages image page-count)
```

Returns a new image with animated page count metadata set to `page-count`.

`page-count` must be a positive integer. This updates the libvips
`"n-pages"` field on a copied image header and leaves `image` unchanged.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L572-L578)

---

## assoc-page-height

```clojure
(assoc-page-height image frame-height)
```

Returns a new image with animated frame height metadata set to `frame-height`.

`frame-height` must be a positive integer. This updates the libvips
`"page-height"` field on a copied image header and leaves `image`
unchanged.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L580-L587)

---

## assoc-page-delays

```clojure
(assoc-page-delays image delays)
```

Returns a new image with animated frame delay metadata set to `delays`.

`delays` must be a non-empty sequence of integers. When `image` already has
an explicit page count, the number of delay entries must match it.

This writes the libvips `"delay"` field with the correct array type and
leaves `image` unchanged.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L589-L598)

---

## assoc-loop-count

```clojure
(assoc-loop-count image loop-value)
```

Returns a new image with animated loop count metadata set to `loop-value`.

`loop-value` must be a non-negative integer. `0` means loop forever for
formats that use that convention. This leaves `image` unchanged.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L600-L606)

---

## extract-area-pages

```clojure
(extract-area-pages image left top width height)
```

Extracts the same rectangle from each frame of `image`.

For animated images, this crops every logical frame independently and returns
a reassembled animated image with `:pages`, `:page-height`, `:loop`, and
`:delay` preserved. For ordinary single-page images, this behaves like the
normal libvips `extract_area` operation.

Example:

```clojure
(with-open [image   (ops/gifload "input.gif" {:n -1})
            cropped (v/extract-area-pages image 10 7 50 50)]
  (select-keys (v/metadata cropped) [:width :height :pages :page-height]))
```

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L608-L624)

---

## embed-pages

```clojure
(embed-pages image x y width height)
(embed-pages image x y width height opts)
```

Embeds each frame of `image` into a new canvas and returns the result.

For animated images, this applies libvips `embed` to every frame and then
reassembles the result while preserving animation metadata such as `:loop`
and `:delay`. For ordinary single-page images, this behaves like the normal
libvips `embed` operation.

Arity 6 accepts the same options map passed to libvips `embed`.

Options:

|     |     |
| --- | --- |
| key | description |
| `:extend` | How pixels outside the source image are filled, for example `:background` |
| `:background` | Background band values used when `:extend` is `:background` |

Example:

```clojure
(with-open [image  (ops/gifload "input.gif" {:n -1})
            framed (v/embed-pages image 8 8 70 70 {:extend :background
                                                   :background [0 0 0 0]})]
  (v/page-height framed))
```

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L626-L654)

---

## rot-pages

```clojure
(rot-pages image angle)
```

Rotates each frame of `image` by `angle`.

For animated images, this applies libvips `rot` to each frame and
reassembles the result with updated frame geometry and preserved animation
metadata. For ordinary single-page images, this behaves like the normal
libvips `rot` operation.

`angle` accepts the normalized enum keywords from [`ol.vips.enums/angle`](api/ol-vips-enums.adoc#angle),
such as `:d90`, `:d180`, or `:d270`.

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L656-L667)

---

## assemble-pages

```clojure
(assemble-pages frames)
(assemble-pages frames {:keys [loop delay] :as _opts})
```

Stacks `frames` into one animated image and annotates the result as multi-page.

`frames` must be a non-empty collection of images. Each input image becomes
one animation frame. All frames must have the same width and height. The
returned image has `:pages` set to the number of frames and `:page-height`
set to the height of each input frame.

Arity 2 accepts an options map.

Options:

|     |     |
| --- | --- |
| key | description |
| `:loop` | Animated loop count as a non-negative integer |
| `:delay` | Non-empty sequence of integer frame delays, one per frame |

`assemble-pages` requires at least one frame. When `:delay` is provided, its
entry count must match the number of frames.

Example:

```clojure
(with-open [animated (v/assemble-pages [frame-a frame-b frame-c]
                                       {:loop 2
                                        :delay [80 120 160]})]
  (select-keys (v/metadata animated) [:pages :page-height :loop :delay]))
```

[source,window=_blank](https://github.com/outskirtslabs/vips/blob/main/src/ol/vips.clj#L669-L700)
