Animating the evoloution of football kits using R


I’m loving the magick package at the moment. Reading through the vignette I spotted the image_morph() function. In this post I experiment with the function to build the GIF below that shows the changes in the England football first kit over time, using images from the excellent Historical Football Kits website.


The Historical Football Kits website has a detailed section on England kits spread over six pages, starting from the first outfits used in 1872. Each pages includes some interesting discussion - and importantly for this post - images of the kits.

We can use the read_html() from the xml2 package and map() from purrr to read and save the source code of each page.


htmls <- c( 
) %>%

From the source code we can then find the URLs of each kit image files using html_nodes() and html_attr() from rvest. I used purrr’s map_dfr() to store the links in a tibble and then dropped rows that do not contain kit image links or are images of away kits, kits used in single game or links to shops to buy replicas. This filtering was based on the image label or image URL and performed with the aid of the str_detect() function from stringr.

scrape_img_url <- function(html){
  html %>%
    html_nodes(".float p , .float img") %>%
    html_attr("src") %>%
    as_tibble() %>%
    set_names("img_url") %>%
    mutate(label = html %>% 
             html_nodes(".float p , .float img") %>%
             html_text() %>%
             c(., NA) %>%

d1 <- htmls %>% 
  map_dfr(scrape_img_url) %>%
  filter(str_detect(string = img_url, pattern = "/international/england"),
         !str_detect(string = label, pattern = "change|alternate|Alternate|Change"),
         !str_detect(string = label, pattern = " v |Third"),
         !str_detect(string = img_url, pattern = "lithuania|italy|yellow|red"))
##                                               img_url     label
## 1      /international/england/images/england-1872.gif      1872
## 2      /international/england/images/england-1882.gif 1879-1900
## 3      /international/england/images/england-1900.gif 1900-1914
## 4 /international/england/images/england-1920-1932.gif 1920-1930
## 5      /international/england/images/england-1921.gif 1930-1934
## 6      /international/england/images/england-1934.gif      1934

Given these URLs I then downloaded each of the images which are stored in a single R object kits


kits <- d1 %>%
  mutate(img_url = paste0("", img_url),
         img_url = str_replace(string =img_url, pattern =" ", replacement = "%20")) %>%
  select(img_url) %>%
  map(image_read) %>%

Typing kits into R will display each kit in the RStudio viewer (it will quickly run through each image). The console displays summary information for each image in the kits object.

> kits
   format width height colorspace filesize
1     GIF   170    338       sRGB        0
2     GIF   170    338       sRGB        0
3     GIF   170    338       sRGB        0
4     GIF   170    338       sRGB        0
5     GIF   170    338       sRGB        0
6     GIF   170    338       sRGB        0
7     GIF   170    338       sRGB        0
8     GIF   170    338       sRGB        0
9     GIF   170    338       sRGB        0
10    GIF   170    338       sRGB        0

Annotating Images

Before creating any GIF I wanted add annotations for the year and the copyright information. To do this I first created a border using image_border() in magick and then image_annotate() to add the text. I wrapped these edits into an add_text() function and then applied each to the kit images.

add_text <- function(img, label){
  img %>%
    image_border(geometry = "10x60", color = "white") %>%
    image_chop("0x45") %>%
    image_annotate(text = label, gravity = "north") %>%
      text = "Animation by @guyabelguyabel", gravity = "south", location = "+0+45"
    ) %>%
      text = "Images are Copyright of Historical\nFootball Kits and reproduced by\nkind permission.",
      gravity = "south"

for(i in 1:length(kits$img)){
  kits$img[i] <- add_text(img = kits$img[i], label = d1$label[i])
  # add extra border to make final images square
  kits$img[i] <- image_border(image = kits$img[i], geometry = "85x1", color = "white")

Creating a GIF

The final step was to bind together the set of images in an animated GIF with smooth transition images between each frame. To do this I used the image_morph() twice. First to repeat the same image so that the GIF would remain stable for a few frames (kits_morph1 below). Then again to create a set of morphing images between successive kits (kits_morph0 below). The full set of frames were stored in kits_ani

kits_ani <- image_morph(c(kits$img[1], kits$img[1]), frames = 4)
for(i in 2:length(kits$img)){
  kits_morph0 <- image_morph(c(kits$img[i-1], kits$img[i]), frames = 4)
  kits_morph1 <- image_morph(c(kits$img[i], kits$img[i]), frames = 4)
  kits_ani <- c(kits_ani, kits_morph0)
  kits_ani <- c(kits_ani, kits_morph1)

To create an animation I passed the set of frames in the kits_morph object to the image_animate() and image_write() functions to give the image above.

kits_ani %>%
  image_animate(fps = 10) %>%
  image_write(path = "england.gif")

This bit of code can take a while to execute if the are many frames (see my comments towards the end of the post).

Club Teams

Similar code as above can be used to create images for club teams. I tried this out for the mighty Reading. As the Reading kits on Historical Football Kits are on only one page and includes only home kits, finding the image URLs was much easier…

d1 <- read_html("") %>%
  scrape_img_url() %>%
  filter(str_detect(string = img_url, pattern = "/Reading"),
         !str_detect(string = img_url, pattern = "unknown")) %>%
    label = str_replace_all(string = label,
                            pattern = "[:alpha:]|\\s", 
                            replacement = "")

I could then run the same code as above to scrape the images, annotate the year and copyright information and build the GIF.

Ian Holloway - we had hoops first.

I did have trouble creating a GIF’s when I used more frames to morph images between successive kits, for example when using frames = 10 in image_morphi(). I could not consistently replicate the error, but I suspect it is something related to the memory size - my R session would freeze when passing image_animate() or image_write() on the kits_ani R object when it contained a large number of images.

Guy Abel
Guy Abel

My research interests include migration, statistical programming and demographic methods.