Skip to main content
AI in Production 2026 is now open for talk proposals.
Share insights that help teams build, scale, and maintain stronger AI systems.
items
Menu
  • About
    • Overview 
    • Join Us  
    • Community 
    • Contact 
  • Training
    • Overview 
    • Course Catalogue 
    • Public Courses 
  • Posit
    • Overview 
    • License Resale 
    • Managed Services 
    • Health Check 
  • Data Science
    • Overview 
    • Visualisation & Dashboards 
    • Open-source Data Science 
    • Data Science as a Service 
    • Gallery 
  • Engineering
    • Overview 
    • Cloud Solutions 
    • Enterprise Applications 
  • Our Work
    • Blog 
    • Case Studies 
    • R Package Validation 
    • diffify  

External Graphics with knitr

Author: Colin Gillespie

Published: February 23, 2021

tags: r, knitr, graphics, rmarkdown

This is part three of our four part series

  • Part 1: Specifying the correct figure dimension in {knitr}.
  • Part 2: What image format should you use for graphics.
  • Part 3: Including external graphics in your document (this post).
  • Part 4: Setting default {knitr} options.

In this third post, we’ll look at including eternal images, such as figures and logos in HTML documents. This is relevant for all R markdown files, including fancy things like {bookdown}, {distill} and {pkgdown}. The main difference with the images discussed in this post, is that the image isn’t generated by R. Instead, we’re thinking of something like a photograph. When including an image in your web-page, the two key points are

  • What size is your image?
  • What’s the size of your HTML/CSS container on your web-page?

Sizing your image

Let’s suppose we want to include a (pre-covid) picture of the Jumping Rivers team. If you click on the original picture, you’ll see that it takes a second to load and also fills the page. This is a very strong hint that the dimensions need to be changed. On our web-page, we intend to place the image as a featured image (think the header image of a blog post). The dimensions of this is 400px by 400px (typically the resolution will be larger than this).

We can determine the image dimensions of our original image using the {imager} package

library("imager")
original = imager::load.image("office.jpeg")
d = dim(original)[1:2]
d
#> [1] 4409 3307

As you might have guessed, the dimensions are far larger than required, 4409px by 3307px instead of 400px square. This, in turn, corresponds to an overly large file size

fs::file_info("office.jpeg")$size
#> 3.4M

My general rule of thumb, is

don’t lose sleep about images less than 200kb

Also, as the original image isn’t square, we can’t just squash the image into a square box, as this will distort the picture.

Instead, we’ll scale one dimension to match 400px

scale = max(d / 400)
img = imager::resize(original, 
                     imager::width(original) / scale, 
                     imager::height(original) / scale,
                     interpolation_type = 6)
img
#> Image. Width: 399 pix Height: 300 pix Depth: 1 Colour channels: 3

then pad the other dimension with a white background

square_img = imager::pad(img, 
                         nPix = 400 - height(img), 
                         axes = "y", 
                         val = "white")
imager::save.image(square_img, file = "office_square.jpeg")
square_img
#> Image. Width: 399 pix Height: 400 pix Depth: 1 Colour channels: 3

As we have scaled the image to have the correct dimensions, the file size is also smaller

fs::file_info("office_square.jpeg")$size
#> 24.4K

Do you use Professional Posit Products? If so, check out our managed Posit services

Including images in an R markdown File

We can include an image using {knitr} and the include_graphics() function, e.g.

knitr::include_graphics("office_square.jpeg")

Typically the chunk would use echo = FALSE as we don’t want to see the actual R code. As the image isn’t being generated by R, the chunk arguments fig.width and fig.height are redundant here - these arguments only affect graphics that are generated by R.

There are four core arguments for manipulating how and where the image is specified

  • fig.align. Default left. In this page, I’ve set that to center.
  • output.width & output.height control the output width / height, in pixels or percentages. The % refers to the percent of the HTML container.
  • fig.retina takes a value 1 or 2, with the default being 2 (discussed below)

The arguments to control the output width / height are output.width / output.height, which accept sizes as pixels or percentages, using the string % or px as a suffix. The % refers to the percent of the HTML container. The crucial thing to note is that we if we size the width & height smaller than the actual image, then the browser will scale the image, but image will still be the same size, potentially impacting page speeds.

Setting out.width="200px" and fig.retina=1 (we’ll cover retina below), will put our 400px square image in a 200px box. If you right click on the image and select View Image, you’ll see that the image is large than the display below.

# out.width="200px", fig.retina = 1
knitr::include_graphics("office_square.jpeg")
The JR office at 200px

Setting out.width="400px" and fig.retina=1 displays the 400px image in a 400px box (depending on your screen).

# out.width="400px", fig.retina = 1
knitr::include_graphics("office_square.jpeg")
The JR office at 400px

When thinking about images on web-pages, the main things to consider are

  • figures dimensions: do they match your HTML box
  • file size: too large and your page speed will become too slow.

In the above example, office_square.jpeg is less 30KB and has the correct dimensions. If the file is only 30KB, life is too short to worry about it being too large. However, the original image, offce.jpeg was 3.4MB. This would have a detrimental effect on download speeds and just annoy viewers.

My rough rule of thumb is not to worry about image sizes on a page, if the total size of all images is less than 1MB. For blog posts, I rarely worry. However, for our course pages that has images associated with our training courses, then a little more care needs to be taken.

The Retina Setting

As we discussed in previous blog posts, the argument fig.retina allows you to produce a figure that looks crisper on higher retina displays. When we create an R graphic, fig.retina automatically doubles the resolution. To achieve this improved quality in external graphics, we need to double the dimensions of the image we wish to include. In our example above, this would mean creating an image of 800px by 800px to put into our 400px by 400px box, e.g.

## Double the size for retina
scale = max(d / 800)
img = imager::resize(original, 
                     width(original) / scale, 
                     height(original) / scale,
                     interpolation_type = 6)
square_img = imager::pad(img, 
                         nPix = 800 - height(img), 
                         axes = "y", 
                         val = "white")
imager::save.image(square_img, file = "office_square-retina.jpeg")

In both {knitr} and {rmarkdown}, the default value of fig.retina is 2.

When we omit the out.width the image width output is the the minimum of the

  • the HTML container
  • the width of the image / fig.retina

Hence, if we have a chuck with no options, this figure

# Image is 400px, output at 200px due to fig.retina=2
knitr::include_graphics("office_square.jpeg")
Image with fig.retina=1

is output at 200px wide, while the retina image is output at 400px

# Image is 800px, output at 400px due to fig.retina=2
knitr::include_graphics("office_square-retina.jpeg")
Image with fig.retina=2

Resizing your image

The eagle eyed readers amongst you will have noticed that we slipped an additional value in the imager::resize() function above, interpolation_type = 6. Like most users of software, I prefer keeping values to the their default, however, in this case the default produced terrible results.

The interpolation_type argument controls the method of interpolation. The default value of 1 corresponds to nearest-neighbour interpolation, 5 corresponds to cubic interpolation (which is the default value the Gimp uses), and 6 to lanczos.

According to this stackoverflow answer,

(lanczos) is much like cubic except that instead of blurring, it (lanczos) creates a “ringing” pattern. The benefit is that it can handle detailed graphics without blurring like the cubic filters.

If we put the output of each method side-by-side, the nearest-neighbour version doesn’t capture the lines quite as well as Lanczos

Comparison of scaling methods.
Jumping Rivers Logo

Recent Posts

  • Start 2026 Ahead of the Curve: Boost Your Career with Jumping Rivers Training 
  • Should I Use Figma Design for Dashboard Prototyping? 
  • Announcing AI in Production 2026: A New Conference for AI and ML Practitioners 
  • Elevate Your Skills and Boost Your Career – Free Jumping Rivers Webinar on 20th November! 
  • Get Involved in the Data Science Community at our Free Meetups 
  • Polars and Pandas - Working with the Data-Frame 
  • Highlights from Shiny in Production (2025) 
  • Elevate Your Data Skills with Jumping Rivers Training 
  • Creating a Python Package with Poetry for Beginners Part2 
  • What's new for Python in 2025? 

Top Tags

  • R (236) 
  • Rbloggers (182) 
  • Pybloggers (89) 
  • Python (89) 
  • Shiny (63) 
  • Events (26) 
  • Training (23) 
  • Machine Learning (22) 
  • Conferences (20) 
  • Tidyverse (17) 
  • Statistics (14) 
  • Packages (13) 

Authors

  • Amieroh Abrahams 
  • Tim Brock 
  • Aida Gjoka 
  • Shane Halloran 
  • Russ Hyde 
  • Myles Mitchell 
  • Theo Roe 
  • Colin Gillespie 
  • Gigi Kenneth 
  • Osheen MacOscar 
  • Sebastian Mellor 
  • Keith Newman 
  • Pedro Silva 

Keep Updated

Like data science? R? Python? Stan? Then you’ll love the Jumping Rivers newsletter. The perks of being part of the Jumping Rivers family are:

  • Be the first to know about our latest courses and conferences.
  • Get discounts on the latest courses.
  • Read news on the latest techniques with the Jumping Rivers blog.

We keep your data secure and will never share your details. By subscribing, you agree to our privacy policy.

Follow Us

  • GitHub
  • Bluesky
  • LinkedIn
  • YouTube
  • Eventbrite

Find Us

The Catalyst Newcastle Helix Newcastle, NE4 5TG
Get directions

Contact Us

  • hello@jumpingrivers.com
  • + 44(0) 191 432 4340

Newsletter

Sign up

Events

  • North East Data Scientists Meetup
  • Leeds Data Science Meetup
  • Shiny in Production
British Assessment Bureau, UKAS Certified logo for ISO 9001 - Quality management British Assessment Bureau, UKAS Certified logo for ISO 27001 - Information security management Cyber Essentials Certified Plus badge
  • Privacy Notice
  • |
  • Booking Terms

©2016 - present. Jumping Rivers Ltd