---
title: "Use images in ggplot2"
vignette: >
  %\VignetteIndexEntry{Use images in ggplot2}
  %\VignetteEngine{quarto::html}
  %\VignetteEncoding{UTF-8}
format:
  html:
    toc: true
    theme: cosmo
    highlight: github
---

`ggimage` lets you use images in `ggplot2`. An image can be a file on disk, a
`magick` image, or a URL, and it can be used as a point glyph, as a background,
or as an inset.

Every image layer is an ordinary `ggplot2` layer: it takes `mapping` and `data`,
it respects `stat`, `position` and `inherit.aes`, and it can be combined with any
other layer, scale, facet or theme. The geoms in this package differ only in
*where the image comes from* and *how it is sized*.

```{r setup}
#| echo: false
#| results: hide
#| message: false
library("ggplot2")
library("ggimage")
```

## Arguments shared by the image layers

The image geoms accept the same arguments as any other ggplot2 geom, plus the
ones below. Reading this list once makes the rest of the document short, since
the sections that follow only describe what is specific to each geom.

**Aesthetics**

- `image` (or the `image` argument for a single image). The value may be a file
  path, a URL, or a `magick` image, and it may be mapped per row, so different
  points can use different images. A character column holds one path or URL per
  row; a list-column holds one `magick` image per row, as in
  `df$image <- list(img1, img2)`, and mapping it with `aes(image = image)` draws
  each row's own image. A row whose `image` is `NA` is drawn as nothing rather
  than as an error.
- `size`. How large the image is, as a **fraction of the panel** -- `size = .05`
  is five percent of the width of the panel by default. Because it is relative
  to the panel, the same code gives the same visual result whatever the data
  range or the size of the output device.
- `width` and `height`. An explicit size, in the same panel units as `size`.
  Either can be mapped per row. Giving one of them keeps the aspect ratio of the
  image; giving both draws the image in exactly that box, which distorts it if
  the box has a different ratio. They take precedence over `size`.
- `colour` and `alpha`. Applied per row: `colour` tints the image and `alpha`
  sets its opacity. `alpha` is an ordinary ggplot2 aesthetic, so its default
  continuous scale maps the data range onto `[0.1, 1]`; add
  `scale_alpha_identity()` to pass your values through unchanged.
- `angle`. Rotates the image, in degrees, counter-clockwise.

**Layer parameters**

- `by`. Whether `size` refers to the `"width"` (the default) or the `"height"`
  of the image. It has no effect on `width` and `height`.
- `nudge_x`, `nudge_y`. Shift the images by a fixed amount, in data units. The
  data are not changed, so this is the way to offset an image from the point it
  belongs to.
- `hjust`. Where the image sits horizontally relative to its `x`. The default,
  `0.5`, centres it; `0` (or `"left"`) puts its left edge on the point and `1`
  (or `"right"`) its right edge.
- `asp`. Divides the width-to-height ratio of an image sized by `size`, so the
  image is drawn taller by that factor. The default, `1`, keeps the proportions
  the image has on disk.
- `use_cache`. Whether to reuse prepared images across draws (the default). See
  [The image cache](#the-image-cache) for how to bound it.
- `image_fun`. A function applied to the `magick` image before it is drawn, for
  instance to flatten a transparent background or to recolour it.

## Drawing images at data points

### geom_image

```{r geom-image}
#| warning: false
set.seed(2017-02-21)
img <- list.files(system.file("extdata", package = "ggimage"),
                  pattern = "png", full.names = TRUE)
d <- data.frame(x = rnorm(10),
                y = rnorm(10),
                image = sample(img, size = 10, replace = TRUE))

## one image, used by the sections below
logo <- system.file("extdata/Rlogo.png", package = "ggimage")

ggplot(d, aes(x, y)) + geom_image(aes(image = image), size = .05)
ggplot(d, aes(x, y)) + geom_image(aes(image = image), size = .05, by = "height")

ggplot(d, aes(x, y)) + geom_image(image = d$image[1])
d$size <- seq(.05, .15, length.out = 10)
ggplot(d, aes(x, y)) + geom_image(aes(image = image, size = I(size)))

ggplot(d, aes(x, y)) + geom_image(aes(image = image), color = "firebrick")
```

`geom_image()` is the layer everything else builds on. The first plot maps
`image` to a column, so each point gets its own picture; the second asks for the
same size measured against the height of the panel instead of its width, which
is the right choice when the panel is taller than it is wide. The third passes a
single image as a plain argument instead of mapping it, which draws the same
picture at every point.

The fourth maps `size` to a column. Wrapping it in `I()` matters: without it
ggplot2 would build a legend for the size, and the values would be rescaled
rather than used as the fractions of the panel you wrote. The last plot sets a
fixed `colour`, which tints every image.

`image` may also be a column of URLs, or a list of `magick` images, so an image
can come from anywhere without being written to disk first.

`size` is one way to control the dimensions; `width` and `height` are the other.
They are read per row, so a single layer can draw images of different shapes.
Giving only one of them preserves the aspect ratio of each image; giving both
stretches it into the requested box.

```{r geom-image-dimensions}
#| warning: false
d$w <- seq(.02, .1, length.out = 10)
ggplot(d, aes(x, y)) + geom_image(aes(image = image, width = w))

## width and height together stretch the image into the requested box
ggplot(d, aes(x, y)) + geom_image(aes(image = image, width = .12, height = .04))
```

`colour` and `alpha` are applied to each image in turn, which makes it easy to
fade an image into the background or to highlight a subset of the rows. Note the
`scale_alpha_identity()` in both plots below: `alpha` is an ordinary ggplot2
aesthetic, and its default continuous scale would compress the values you give
into `[0.1, 1]`.

```{r geom-image-colour}
#| warning: false
d$fade <- seq(.2, 1, length.out = 10)
ggplot(d, aes(x, y)) + geom_image(aes(image = image), size = .08, alpha = .5) +
    scale_alpha_identity()

ggplot(d, aes(x, y)) + geom_image(aes(image = image, alpha = fade), size = .08) +
    scale_alpha_identity()
```

### geom_image_repel

`geom_image_repel()` draws the same images as `geom_image()` but moves them so
that they no longer overlap, which keeps the images readable when the points are
close together. The positions in the data are untouched -- only the drawing is
displaced -- so the axes, the scales and any other layer keep their meaning.

```{r geom-image-repel}
#| warning: false
d2 <- data.frame(x = c(.50, .52, .51), y = c(.50, .51, .49))
p <- ggplot(d2, aes(x, y, image = logo)) +
    scale_x_continuous(limits = c(0, 1)) +
    scale_y_continuous(limits = c(0, 1))

## as they are, the images overlap
p + geom_image(width = .12)

## ... and are pushed apart by geom_image_repel()
p + geom_image_repel(width = .12)
```

It takes everything `geom_image()` takes, plus four parameters that control the
displacement:

- `max.iter` (default `100`). How many displacement iterations to run. Setting
  it to `0` keeps the original coordinates, which is useful to check how much of
  the layout the repulsion is actually responsible for.
- `force` (default `0.1`). The fraction of the overlap that is resolved in one
  iteration: each image of an overlapping pair moves by `force * overlap / 2`.
  Larger values converge in fewer iterations but produce bigger jumps, which can
  look arbitrary.
- `box.padding` (default `0`). Extra space kept around every image box, in the
  same units as `width`. Raise it when the images are legible but still feel
  cramped.
- `direction` (default `"both"`). Which axes the images may move along, one of
  `"both"`, `"x"` or `"y"`. Overlap is always detected on both axes; the
  parameter only restricts the movement, so `direction = "x"` slides images
  horizontally until they clear each other.

The displacement is deterministic: the same data and the same parameters give
the same layout on every run, and no network access is involved once the images
are cached. That also means a plot does not change shape when it is redrawn.

### geom_subview

`geom_subview()` embeds a whole plot -- a pie chart, a legend, a small `ggplot`
-- as an inset at a data point. Because `width` and `height` are in **data
units**, the insets stay the size you asked for whatever the panel is, and they
move with the data when the scales change.

```{r geom-subview}
library(tibble)
dd <- data.frame(x = LETTERS[1:3], y = 1:3)
pie <- ggplot(dd, aes(x = 1, y, fill = x)) + geom_bar(stat = "identity", width = 1) +
    coord_polar(theta = "y") +
    theme_void() + theme(legend.position = "none") + theme_transparent()

df <- tibble(x = sample(2:9),
             y = sample(2:9),
             width = sample(seq(0.5, 3, length.out = length(x))),
             pie = list(pie))
p <- ggplot(data = data.frame(x = c(0, 10), y = c(0, 10)), aes(x, y)) + geom_blank()
p + geom_subview(aes(x = x, y = y, subview = pie, width = width, height = width), data = df)
```

The subview goes in a list column, one plot per row, and `x`, `y`, `width` and
`height` are mapped from the data frame as usual. The insets are drawn with a
transparent background (`theme_transparent()`, see [Themes](#themes)), so the
plot underneath shows through instead of being hidden by a grey panel.

The subview may also be passed as the first argument, which is the form the
published examples use: `geom_subview(pie, x = 2, y = 2)` draws one inset at
`(2, 2)`. In that form `width` and `height` default to `0.1`.

## Sub-views from other graphics systems

`geom_subview()` accepts anything that `ggplotify::as.grob()` can turn into a
grid grob, so an inset does not have to be a `ggplot`. A base graphics plot, a
recorded plot, a grob, or an image can be placed as a sub-view just as easily,
and plot objects from other packages work whenever `as.grob()` has a method for
them.

| sub-view | how to pass it |
| --- | --- |
| base graphics | a `function()` that draws it, or a one-sided `formula` such as `~ plot(x, y)` |
| recorded plot | `function() replayPlot(rec)` |
| `grid` grob | the grob itself |
| `ggplot` | the plot object; subclasses such as `ggtree` objects work the same |
| `magick` | the image |

A base plot is a side effect on a graphics device rather than an object, so it
has to be handed over as something that can be *drawn*: a function, or a formula
whose right-hand side is the plotting call.

```{r subview-base}
#| warning: false
#| fig-width: 7
#| fig-height: 4.8
set.seed(2026)
svd <- data.frame(x = rep(c(0, 2, 4), 2), y = rep(c(0, 2), each = 3))
svd$hist <- lapply(1:6, function(i) function() {
    hist(rnorm(300, mean = i), main = "", xlab = "", ylab = "",
         col = i, border = "white", axes = FALSE)
})
ggplot(svd, aes(x, y)) + geom_point(size = 2) +
    geom_subview(aes(x = x, y = y, subview = hist), data = svd,
                 width = 1.7, height = 1.7) +
    xlim(-1.2, 5.2) + ylim(-1.2, 3.2) + coord_fixed() + theme_bw()
```

The same `as.grob()` conversion runs for every row, so one layer can hold
sub-views built in different ways.

```{r subview-systems}
#| warning: false
#| fig-width: 7
#| fig-height: 5.6
svd <- data.frame(x = 1:3, y = 0)
svd$subview <- list(
    function() plot(cos(seq(0, 2 * pi, length.out = 50)), type = "l",
                    axes = FALSE, ann = FALSE),
    ggplot(mtcars, aes(wt, mpg)) + geom_point(),
    magick::image_read(system.file("extdata/Rlogo.png", package = "ggimage"))
)
labels <- c("base graphics", "ggplot", "magick")
names(svd$subview) <- labels

ggplot(svd, aes(x, y)) + geom_blank() +
    geom_subview(aes(x = x, y = y, subview = subview), data = svd,
                 width = .8, height = .8) +
    geom_text(aes(x, y, label = label),
              data = data.frame(x = 1:3, y = -.7, label = labels),
              vjust = 1, size = 3.2) +
    xlim(.5, 3.5) + ylim(-1.2, 1.2) + coord_fixed() + theme_void()
```

Two things are worth knowing before relying on this.

**The panel still needs a scale.** `geom_subview()` draws with
`annotation_custom()`, which carries no `x` or `y` aesthetic of its own. When it
is the only layer in the plot, the panel keeps its default `(0, 1)` range and
only the insets that fall inside that range are drawn. Add a `geom_blank()`, a
`geom_point()` or a `geom_text()` -- as above -- or set the limits explicitly.

**A sub-view given as a function is drawn afresh on every draw.** The conversion
happens when the plot is drawn, so the function has to be able to run on its own,
and the usual closure trap applies: build the list of functions with `lapply()`
rather than a `for` loop, or every function will use the last value of the loop
variable.

For a plot that already exists as a recorded plot, replay it inside a function:

```r
dev.control(displaylist = "enable")   # otherwise the recording comes out empty
plot(1:10)
rec <- recordPlot()

df$subview <- list(function() replayPlot(rec))
```

`recordPlot()` returns an empty recording unless the display list was enabled,
which is why `dev.control()` is there.

## Images as backgrounds

### geom_bgimage

Use an image as the background of the plot panel.

```{r geom-bgimage}
#| warning: false
ggplot(iris, aes(Petal.Length, Petal.Width)) +
    geom_bgimage(logo) + geom_point()
```

The layer is added first, so everything drawn after it appears on top. By
default the image is stretched to fill the panel, which is usually what you want
for a texture, but it does mean the image is distorted if its ratio differs from
the panel's.

Give `pixel` to place the image in data coordinates instead: the chosen pixel of
the image -- `c(col, row)` counted from the top-left corner -- is anchored to the
data point `(x, y)`, and `scale` sets the number of data units per image pixel.
The anchoring does not depend on the size of the image or of the panel, so the
image keeps its place when the plot is resized or the scales change.

```{r geom-bgimage-anchor}
#| warning: false
info <- magick::image_info(magick::image_read(logo))

ggplot(iris, aes(Petal.Length, Petal.Width)) +
    geom_bgimage(logo, pixel = c(1, info$height), x = 0, y = 0,
                 scale = 2.5 / info$width) +
    geom_point() +
    scale_x_continuous(limits = c(-0.4, 7), expand = c(0, 0)) +
    scale_y_continuous(limits = c(-0.3, 2.6), expand = c(0, 0)) +
    coord_fixed()
```

Here the bottom-left corner of the image (`col = 1`, `row = height`) is pinned to
the origin, and `scale = 2.5 / info$width` makes the image 2.5 data units wide.
Since the anchoring is in data units, `coord_fixed()` is needed to keep the image
from being stretched by unequal axes.

### ggbackground

`ggbackground()` and `geom_bgimage()` both fill the panel and stretch the image
to it. The difference is how they are applied: `ggbackground(p, img)` takes a
plot and returns one, so it can be used on a plot that is already built, whereas
`geom_bgimage()` is a layer and sits behind whatever is added after it.

```{r ggbackground}
#| warning: false
ggbackground(ggplot(iris, aes(Petal.Length, Petal.Width)) + geom_point(), logo)
```

The background is inserted as the first layer rather than as a grob wrapping the
whole plot, so anything added to the result afterwards is still positioned by the
plot's own coordinate system. Extra arguments are passed on to `geom_image()`,
so `ggbackground(p, img, colour = "grey80")` tints the background.

## Images from remote services

Several geoms take the *name* of an image and fetch it for you: a Pokemon, an
emoji, an icon, a flag, or a taxonomic silhouette. They all use the same `image`
aesthetic and the same sizing arguments as `geom_image()`, and anything that is a
URL can also be given to `geom_image()` directly.

Downloaded images are cached, so a plot that is redrawn -- on a resize, or in a
loop of `ggsave()` calls -- does not download them again.

### geom_pokemon

```{r geom-pokemon}
#| warning: false
ggplot(d, aes(x, y)) + geom_pokemon(aes(image = ifelse(x > 0, "pikachu", "tauros")), size = .1)
```

The name is the Pokemon's name in lower case, and `list.pokemon()` returns the
names that are available.

### geom_emoji

Emoji are named by code point, e.g. `1f600` for a grinning face.

```{r geom-emoji}
#| warning: false
set.seed(123)
iris2 <- iris[sample(1:nrow(iris), 30), ]
model <- lm(Petal.Length ~ Sepal.Length, data = iris2)
iris2$fitted <- predict(model)

ggplot(iris2, aes(x = Sepal.Length, y = Petal.Length)) +
  geom_linerange(aes(ymin = fitted, ymax = Petal.Length),
                 colour = "purple") +
  geom_abline(intercept = model$coefficients[1],
              slope = model$coefficients[2]) +
  geom_emoji(aes(image = ifelse(abs(Petal.Length - fitted) > 0.5, "1f622", "1f600")))
```

Because the image can be computed per row, an emoji layer is a compact way to
label the outcome of a calculation: here the face depends on the residual of
each point.

### geom_icon

```{r geom-icon}
#| warning: false
d$icon <- sample(c("power", "wifi", "pie-chart"), 10, replace = TRUE)
ggplot(d, aes(x, y)) + geom_icon(aes(image = icon), size = .1)
```

See <https://ionic.io/ionicons> for the icon names; `list.icon()` returns the ones
this version knows about.

### geom_flag

Flags are named by their two-letter code, or by a subdivision code such as
`GB-ENG` for England.

```{r geom-flag}
#| warning: false
f <- system.file("extdata/medals.txt", package = "ggimage")
medals <- read.table(f, header = TRUE)
p <- ggplot(medals, aes(Country, count)) + geom_col(aes(fill = medal), width = .8)

p + geom_flag(y = -2, aes(image = code)) +
    coord_flip() + expand_limits(y = -2) +
    scale_fill_manual(values = c("Gold" = "gold", "Bronze" = "#cd7f32", "Silver" = "#C0C0C0"))
```

Note the `y = -2` here: it is a fixed position for every flag rather than a
mapping, and `expand_limits()` makes room for it below the bars. `list.flag()`
returns the available codes.

### geom_worldcup2018

`geom_worldcup2018()` is `geom_flag()` restricted to the 32 teams of the 2018
FIFA World Cup, which is a convenient way to draw a set of flags without
looking up any codes. A name outside that list is drawn as nothing, so an
unexpected value shows up as a gap rather than as a wrong flag.

```{r geom-worldcup2018}
#| warning: false
wc <- expand.grid(x = 1:8, y = 1:4)
wc$country <- list.worldcup2018()

ggplot(wc, aes(x, y)) +
    geom_worldcup2018(aes(image = country), size = .1) +
    coord_fixed() + theme_void()
```

`list.worldcup2018()` returns the 32 codes the geom accepts. It is a fixed
vector, so unlike the other `list.*()` helpers it does not need a network
connection.

### geom_phylopic

[PhyloPic](https://www.phylopic.org/) hosts free silhouettes of organisms.
`geom_phylopic()` takes a scientific name, or a PhyloPic uid, and resolves it
for you.

```{r geom-phylopic}
#| warning: false
sp <- c("Canis lupus", "Ursus maritimus", "Homo sapiens", "Gallus gallus",
        "Bos taurus", "Panthera leo", "Loxodonta africana",
        "Drosophila melanogaster")
species <- data.frame(x = seq_along(sp), y = 1, species = sp)

ggplot(species, aes(x, y)) +
    geom_phylopic(aes(image = species), size = .12) +
    scale_x_continuous(breaks = seq_along(sp), labels = sp) +
    ylim(.7, 1.3) +
    theme_void() + theme(axis.text.x = element_text(size = 8, angle = 20, hjust = 1))
```

Unlike the other remote geoms, `geom_phylopic()` has its own default `image_fun`,
which clears the background of the SVG rather than leaving it white. Pass your
own `image_fun` to change that.

`phylopic_uid()` is the lookup behind it. A name can match several silhouettes,
and `seed` picks one of them, so the same name gives the same image on every
run. A name that resolves to nothing comes back as `NA`, and the layer draws
nothing for that row.

```{r phylopic-uid}
#| eval: false
phylopic_uid(c("Canis lupus", "Homo sapiens"))
```

A uid can be used directly, which is useful when a name does not resolve, or
when you want a specific drawing.

```{r geom-phylopic-uid}
#| warning: false
uid <- "5036f260-5a0d-42c5-a0bd-9eb0729e54e0" # Canis lupus
ggplot(data.frame(x = 1, y = 1), aes(x, y)) +
    geom_phylopic(aes(image = uid), size = .5) +
    xlim(.5, 1.5) + ylim(.5, 1.5) + theme_void()
```

`autocomplete_name()` suggests full names from a partial one, which is the
quickest way to find a name that will resolve.

```{r autocomplete-name}
#| eval: false
head(autocomplete_name("Canis"), 4)
```

To keep the images, or to draw them without a network connection,
`download_phylopic()` saves them into a directory. The file paths it returns can
then be passed to `geom_phylopic()` just like a uid, and because they are local
files the plot no longer depends on PhyloPic being reachable.

```{r download-phylopic}
#| eval: false
dir <- tempfile()
dir.create(dir)
files <- download_phylopic(c("Canis lupus", "Gallus gallus"), destdir = dir)
basename(files)
```

### Listing the images that are available

Each service has a `list.*()` function that returns the names it accepts, which
is easier than guessing. `list.pokemon()`, `list.icon()` and `list.flag()` ask
the service for its current files, so the three calls below are shown but not
run; `list.worldcup2018()` is the exception, being a fixed vector. The geoms
themselves need no connection once the images are cached.

```{r list-images}
#| eval: false
head(list.pokemon(), 3)
head(list.icon(), 3)
head(list.flag(), 3)
```

## Interactive images

`geom_image_interactive()` and `geom_phylopic_interactive()` are the
[ggiraph](https://davidgohel.github.io/ggiraph/) counterparts of `geom_image()`
and `geom_phylopic()`. They add the ggiraph aesthetics -- `tooltip`, `data_id`,
`onclick` -- to every image, so a plot can be embedded in an HTML page and the
images become hoverable and clickable.

On their own they draw exactly what `geom_image()` draws, which is what makes
them safe to use in a function that may or may not be rendered to HTML:

```{r geom-image-interactive}
#| warning: false
d$id <- seq_len(nrow(d))

ggplot(d, aes(x, y)) +
    geom_image_interactive(aes(image = image), size = .08) +
    theme_void()
```

The interactivity appears once the plot is wrapped in `girafe()`. The chunk
below is not evaluated here, because the ggiraph JavaScript it needs would add
several megabytes to this vignette; run it in your own document to get the
interactive version, and hover the images to see the tooltips.

```{r geom-image-interactive-girafe}
#| eval: false
library(ggiraph)

girafe(ggobj = ggplot(d, aes(x, y)) +
    geom_image_interactive(aes(image = image, tooltip = round(x, 2), data_id = id),
                           size = .08) +
    theme_void())
```

## Animating frames with gganimate

`geom_image()` draws one still image per row of its data, and it knows nothing
about time: a raster grob is drawn when the plot is drawn, and no layer of this
package asks for a second one. Animation therefore comes from
[gganimate](https://gganimate.com/), which owns the states of a plot, the frame
rate, and the rendering of the result. `ggimage` imports nothing from gganimate;
the two packages meet only in the data frame you hand to them. The pattern below
is a *frame-data-driven* one: you decide what the frames are, the data frame
says which image belongs to which frame, and gganimate walks through them.

The first step happens before any plotting. A source animation -- a GIF, a
video, a sequence of frames -- is a container of still images, and that
container has to become files on disk that a row of data can point at.

```{r animate-materialise-frames}
#| eval: false
library(magick)
library(gganimate)

## a small deterministic source animation: four solid-colour frames
frames <- lapply(c("steelblue", "gold", "firebrick", "seagreen"), function(cl) {
    image_blank(200, 200, color = cl) |> image_annotate(cl, size = 24)
})
gif <- image_join(frames)
image_write(gif, "source.gif", format = "gif")   # a multi-frame file, not one PNG

## ... and the same thing read back, one frame per file
src <- image_read("source.gif")
dir.create("frames", showWarnings = FALSE)
paths <- sprintf("frames/frame%02d.png", seq_along(src))
for (i in seq_along(src)) {
    ## magick writes the format of the *object*, not of the file name, so an
    ## explicit format is what keeps these files PNG rather than GIF bytes
    image_write(src[i], paths[i], format = "png")
}
basename(paths)
```

The frame files are now ordinary paths, so the rest is a data frame. It has one
row per (glyph, frame), and a column naming the state -- which frame each row
belongs to. That column is plain input data. It is not an aesthetic of
`geom_image()`, and nothing in this package reads it: it is the column
`transition_manual()` is given.

```{r animate-frame-table}
#| eval: false
## one glyph that stays put, one that moves across the frames
nf <- length(paths)
d <- data.frame(
    glyph = rep(c("fixed", "moving"), each = nf),
    frame = rep(seq_len(nf), times = 2),
    x = c(rep(1, nf), seq(1, 4, length.out = nf)),
    y = rep(1, 2 * nf)
)
d$image <- rep(paths, times = 2)
head(d, 3)
```

The plot is then built from two kinds of layer. A layer whose data has no
`frame` column cannot depend on the frame at all, so it is drawn in every
rendered frame -- here a band and some points that give the animation a
backdrop. The images come from a `geom_image()` layer over the frame table,
with a local `aes()` so that the `x` and `image` of that table are the ones
used.

```{r animate-plot}
#| eval: false
## no frame column here, so this layer persists across states
static <- data.frame(x = 1:4, y = 1)

p <- ggplot(d, aes(x, y)) +
    geom_rect(aes(xmin = 0.5, xmax = 4.5, ymin = 0.6, ymax = 1.4),
              data = data.frame(x = 1), fill = "grey92", inherit.aes = FALSE) +
    geom_point(data = static, size = 1.5) +
    geom_image(aes(image = image), data = d, size = .12) +
    coord_fixed() +
    theme_void()

## the states come from the frame column, not from a ggimage aesthetic
p <- p + transition_manual(frame)

animate(p, nframes = nf, fps = 4, renderer = magick_renderer())
```

`transition_manual()` takes the column directly; there is no ordering to
declare, since the values of `frame` are taken as they come. `nframes` and
`fps` belong to `animate()` rather than to the plot, and they choose the output
schedule -- how long each state is held, and how many frames the result has.
`magick_renderer()` writes the frames through magick, so no other software is
needed to produce the file; gganimate also offers renderers built on external
tools, which are its business and not this package's.

### What this does not promise

- **You preprocess the frames.** Turning a source animation into still files,
  and choosing which of them become states, is done by the magick calls above.
  `ggimage` reads the paths it is given and nothing else; it never opens a GIF
  as a sequence.
- **gganimate drives the states and the rendering.** The transitions, the frame
  rate, the renderer and the output file are all its API. This package supplies
  the images and no part of the animation.
- **A static plot still draws one image per row.** `geom_image(image =
  "sprite.gif")` draws a still image -- magick gives the first frame of the
  file -- and is not playback. Nothing in a plain `ggplot` advances between
  frames, because a raster grob has no clock.
- **The timing of the source is not preserved.** The per-frame delays, the loop
  count and the disposal method of the original file are not read and not
  carried over; the schedule you pass to `animate()` is the schedule you get.
  Frame replacement is discrete, so an image changes from one state to the next
  with nothing in between -- there is no easing of the images themselves,
  whereas the position of a glyph follows whichever transition you chose.

## Legend keys

An image layer has no natural legend key: a raster grob is not a shape a legend
can draw by default, and the images are usually the data rather than a mapped
scale. The global option `ggimage.keytype` chooses what to draw instead, and
accepts four values:

- `"point"` (the default when the option is unset) draws a filled point.
- `"rect"` draws a filled rectangle.
- `"image"` draws an image instead of a shape, tinted by `colour` and faded by
  `alpha` like the images in the panel.
- `"blank"` draws no key at all, which is the behaviour of image layers before
  the key was implemented.

```{r legend-key}
#| warning: false
d$group <- rep(c("up", "down"), each = 5)
key_plot <- ggplot(d, aes(x, y, image = image, colour = group)) +
    geom_image(size = .08) + theme_void()

options(ggimage.keytype = "rect")
key_plot

options(ggimage.keytype = "image")
key_plot + guides(colour = guide_legend(override.aes = list(image = img)))

options(ggimage.keytype = NULL)
```

With `"point"` and `"rect"` the key follows `colour` and `alpha`, so a colour
legend still reads as a colour legend. With `"image"` the key is one of the
pictures, which is the most readable choice when the images *are* the categories
-- here the two groups are told apart by their pictures rather than by a colour
swatch. Which picture comes from the layer's `image` argument: ggplot2 has no
scale to look a per-row `image` up in, so a layer that maps `image` to a column
can only put the same image in every key, and the chunk above hands one image per
group to the guide with `override.aes`. The option is global, so set it once for
the session, or restore the previous value with `options(old)` when you only want
it for one plot.

## Themes

`theme_transparent()` makes the panel and the legend background transparent, so
that a plot can be placed on top of something else -- a background image,
another plot, or a slide with its own colour.

```{r themes-transparent}
#| warning: false
p <- ggplot(iris, aes(Petal.Length, Petal.Width, colour = Species)) +
    geom_point()

## the blue rectangle stands in for whatever is behind the plot
draw_on <- function(plot) {
    grid::grid.newpage()
    grid::grid.rect(gp = grid::gpar(fill = "steelblue", col = NA))
    print(plot, newpage = FALSE)
}

draw_on(p)

## the same plot with the theme applied
draw_on(p + theme_transparent())
```

`theme_nothing()` keeps only the panel: no axes, no labels, and no margin. It
is meant for plots that are drawn *inside* another plot, such as the insets
made by `geom_subview()`, where axes and titles would only be noise.

```{r theme-nothing}
#| warning: false
ggplot(iris, aes(Petal.Length, Petal.Width)) + geom_point() + theme_nothing()
```

## Reading images

`image_read2()` reads an image and, by default, trims the empty margin around
it. Scans and figures exported from other software often carry a wide white
border, which would otherwise shrink the drawing itself to a small part of the
glyph.

```{r image-read2}
#| warning: false
## put a white margin around the logo, as a scan or an exported figure has
bordered <- magick::image_border(magick::image_read(logo), "white", "40x40")
bf <- tempfile(fileext = ".png")
magick::image_write(bordered, bf)

rbind(before = magick::image_info(magick::image_read(bf))[, c("width", "height")],
      after  = magick::image_info(image_read2(bf))[, c("width", "height")])
```

The trim is based on white pixels on the first three channels, so it also works
on images with an alpha channel, and it keeps a matte channel intact. An image
that is entirely white, or one with no margin, is returned unchanged rather than
being cropped to nothing. Use `cut_empty_space = FALSE` to skip the trim
entirely, and `...` to pass extra arguments to `magick::image_read()`, for
instance a `density` for vector files.

## Previewing a plot

`ggpreview()` saves a plot to a file and draws it back into the current device.
The point is to see the plot at the exact size it will be saved, without
leaving a file behind: with no `filename` it uses a temporary file that is
cleaned up at the end of the session.

```{r ggpreview}
#| warning: false
ggpreview(plot = ggplot(iris, aes(Petal.Length, Petal.Width)) + geom_point(),
          width = 4, height = 3)
```

Give `filename` to keep the file as well, in which case the remaining arguments
are passed to `ggsave()` -- so `dpi`, `bg` and the other `ggsave()` parameters
work as usual. It accepts anything `ggplotify` can convert, so a base graphics
plot or a `grid` object works as well as a `ggplot`.

## The image cache

Reading an image is expensive, and a plot is drawn again on every resize, so
`ggimage` caches prepared images. The cache is unbounded by default, which is
fine for a handful of images but not for a plot with thousands of distinct
ones. `set_image_cache_policy()` bounds it.

```{r image-cache-policy}
set_image_cache_policy(capacity = 200, ttl = 60, eviction = "lru")
get_image_cache_policy()[c("base_capacity", "base_ttl", "eviction")]
```

- `capacity` is the number of entries to keep.
- `ttl` is a lifetime in seconds, after which an entry is treated as absent.
- `bytes` is an approximate size cap, measured with `object.size()` plus a
  pixel-based lower bound for `magick` images. It is a guardrail rather than a
  measurement of process memory, since the native image buffers are larger than
  the estimate.
- `eviction` is one of `"lru"`, `"fifo"` or `"none"`; the last one disables
  capacity and byte eviction, leaving only `ttl`.

`NULL` leaves an option unchanged, so a single call can adjust one setting
without disturbing the others. Each value can also be set for the base images
and for the transformed ones separately, with `base_capacity` /
`transform_capacity` and so on, which is useful when a plot maps `colour` or
`angle` and therefore produces many transformed copies of few base images.

`get_image_cache_stats()` reports what the cache is doing, which is the way to
tell whether a policy is worth its cost.

```{r image-cache-stats}
#| warning: false
reset_image_cache_stats()
ggplot(d, aes(x, y)) + geom_image(aes(image = image), size = .05)
get_image_cache_stats()$base
```

The result has a `base` and a `transform` entry, each with `hits`, `misses`,
`evictions`, `expirations`, `entries` and `bytes`. A high `misses` count with few
`entries` means the images are not being reused and the cache is not helping;
`evictions` and `expirations` that climb mean the policy is too tight for the
plot. `reset_image_cache_stats()` clears the counters without dropping the
entries; `get_image_cache_diagnostics()` is the same function under a name that
reads better in debugging code.

```{r image-cache-restore}
options(ggimage.image_cache_capacity = NULL,
        ggimage.image_cache_ttl = NULL,
        ggimage.image_cache_eviction = NULL)
```
