Writing reports in R Markdown allows you to skip painful and error-prone copy-paste in favor of dynamically-generated reports written in R and markdown that are easily reproducible and updateable. R Markdown reports that are heavy on graphs and maps, though, can yield large HTML files that are not optimized for web viewing.
R Markdown offers a wide range of functions and arguments for full control of image sizes but knowing how and when to use them can be daunting particularly given the differences in how external images are handled vs R-generated figures. We assembled this blog post to help guide you through image processing decision-making as you construct your own R Markdown reports.
NOTE 1: This post is focused on the production of HTML documents and some of our conclusions and recommendations may not apply if you're using R Markdown to create a LaTeX document, PDF or Word document.
NOTE 2: Images in the final HTML documents are responsive – meaning that their dimensions may change with changes to the browser view size. In this post, we report image dimensions as they appear at full size on a computer monitor for reference.
Table of contents
- Our examples: one pre-existing image and one dynamically generated plot
- Default settings for including images and figures in R Markdown
- Use
fig.widthandfig.heightfor R-generated figures only - Arguments
out.widthandout.heightapply to both existing images and R-generated figures - Use
dpito change the resolution of images and figures - The
fig.retinaargument is a resolution multiplier - Optimize R-generated images with
optipngorpngquant - Bonus
knitrand R markdown functionality - Summary
Our examples: one pre-existing image and one dynamically generated plot
In this post we'll work with a pre-existing image as well as a dynamically generated plot. The plot is created using the package ggplot2. Moving forward we're going to refer to our uploaded image as image and the R-generated plot as figure. The image was downloaded to our local drive from here and can be used under the Creative Commons CC0 license.
As a starting point, we can compute the dimensions of our raw image using the readPNG function from the package png. There are two ways to grab the dimensions (height and width) of the image.
- Use the
dimfunction: get the dimensions of the image - Use the
attrfunction: get the dimensions of the image as well as some other potentially useful information (color type, dpi, etc).
NOTE: you can use the same process to examine jpegs, simply swap out png for jpeg. For example library(jpeg); readJPEG(img1).
Start by loading the packages
library(knitr) # For knitting document and include_graphics function
library(ggplot2) # For plotting
library(png) # For grabbing the dimensions of png files
Image 1
The raw image on disk has a width of 1000px and height of 667px (300 dpi). Size is 1.2 MB.
img1_path <- "images/sloth1.png"
img1 <- readPNG(img1_path, native = TRUE, info = TRUE)
attr(img1, "info")
## $dim
## [1] 1000 667
##
## $bit.depth
## [1] 8
##
## $color.type
## [1] "palette"
##
## $gamma
## [1] 0.45455

Figure 1
For our R-generated figure, Figure 1, we are using the ggplot2 package and the built in cars data set. Since the figure is being generated on the fly the dimensions and size will depend on the default settings. For this initial view we've set the width to be the same as the image above.
Default settings for including images and figures in R Markdown
Default settings for images and figures are taken from both the knitr and rmarkdown packages. If a setting exists in both packages the rmarkdown value will be used. For example both packages include a default setting for fig.retina. The knitr source code shows a default value of 1 for fig.retina. However if you leave fig.retina blank in your R chunk it will apply the default rmarkdown value of 2.
The table below shows some commonly-used settings from the rmarkdown and knitr packages and their corresponding default values. All settings shown below except for out.width and out.height will default to the rmarkdown value if left blank (rmarkdown does not have settings for out.width and out.height).

The full documentation including default settings for each package can be found below. We’re also including a link to more documentation about the differences in certain settings as they relate to the knitr and rmarkdown packages.
Using the include_graphics function for adding images and figures
We include external images in our R markdown documents using the include_graphics function from the knitr package. Images can also be included using either raw HTML with img tags (<img src = "" />) or using markdown directly (). We are using include_graphics for two reasons. First, the function is document format agnostic – meaning it can work with LaTeX or Markdown documents. Second, although you can technically include an image in a markdown document using standard HTML image tags (<img src = "" />), using include_graphics will respect image settings listed in the R chunks like out.width and out.height.
As mentioned above, the figure is included by creating a new plot on the fly with the ggplot2 package.
How images and figures in the HTML document are affected by using defaults
You'll see below that the default for images is to display them at ½ their original size – you will see below that this is due to the fig.retina = 2 setting (making the same image ½ the size doubles the resolution). This is the default for images using the include_graphics function – original px width * 50%. The external images are unaffected by the fig.width argument (which is set to 7 inches by default). The R-generated figure however is output using the fig.width default of 7 inches.
- Image 1 output (width = 500px and height = 333.5px, 300dpi, 1.2mb on disk): The viewable size in our HTML document is ½ the size of the original image – the default for an external image. The
fig.widthargument has no effect on how external images are rendered. - Figure 1 output (width = 672px (7 inches x 96 dpi) and height = 480px (5 inches x 96dpi), 60kb on disk): Width, height and resolution of the dynamically-generated figure are controlled by
fig.width,fig.heightanddpidefaults.
```{r}
# All defaults
include_graphics(img1_path)
```

```{r}
# All defaults
ggplot(cars, aes(speed, dist)) + geom_point()
```
Use fig.width and fig.height for R-generated figures only
- Default is
fig.width = 7andfig.height = 5(in inches, though actual width will depend on screen resolution). Remember that these settings will default tormarkdownvalues, notknitrvalues.
How images and figures in the HTML document are affected by using fig.width and fig.height:
The fig.width and fig.height arguments only affect the dimensions of R-generated figures as you can see below. The ggplots shown are 2 and 4 inches while the image is still 500px no matter the setting of fig.width.
```{r, fig.width = 2}
# Small fig.width
ggplot(cars, aes(speed, dist)) + geom_point()
```
```{r, fig.width = 4}
# Bigger fig.width
ggplot(cars, aes(speed, dist)) + geom_point()
```
```{r, fig.width = 2}
# Small fig.width
include_graphics(img1_path)
```

```{r, fig.width = 4}
# Bigger fig.width
include_graphics(img1_path)
```

Arguments out.width and out.height apply to both existing images and R-generated figures
- Default is
out.width = NULLandout.height = NULL.
Unlike the fig.width and fig.height arguments which only affect dynamic figures, the out.width and out.height arguments can be used with any type of graphic and conveniently can accept sizes in pixels or percentages as a string with % or px as a suffix.
Keep in mind that the % refers to the percent of the HTML container. For example, if the block of text that the image is in is 1000px wide then the image will be 200px using 20%.
How images and figures in the HTML document are affected by using out.width and out.height:
- For both R-generated figures and external images the graphics dimensions are scaled to match the width/height specified
```{r out.width = "20%"}
include_graphics(img1_path)
```

```{r out.width = "50%"}
include_graphics(img1_path)
```

```{r out.width = "20%"}
ggplot(cars, aes(speed, dist)) + geom_point()
```
```{r out.width = "50%"}
ggplot(cars, aes(speed, dist)) + geom_point()
```
Use dpi to change the resolution of images and figures
In general dpi is a measure of resolution – the higher the dpi, the sharper the image. For the most part, this is less relevant when it comes to HTML files given that the default DPI of computer displays are generally set to 72 or 96 DPI. For the web, using the rmarkdown default of 96dpi should be adequate except for retina screens where you may want to use a multiplier in the form of the fig.retina argument (see below).
When you change the dpi of an R-generated plot, larger numbers result in a larger plot unless other arguments like out.width are specified. With external images, there is no way to increase resolution so knitr compensates by making the same image smaller on the page (the same number of pixels in a smaller area).
Note that the include_graphics function has its own dpi argument. You might think that using dpi=300, for example, in the include_graphics function would have the same effect as using dpi=300 in the chunk, but this is not the case. Using dpi=300 in the include_graphics function appears to override the default chunk setting to make the image 50% width. As a result, using dpi=300 in the chunk on an image that is 1000px yields an image 1000 * 0.5/(300/96) = 160 px wide while using dpi=300 in the include_graphics function results in an image 1000/(300/96) = 320px.
How images and figures in the HTML document are affected by using dpi:
-
For external images the
dpiargument will alter the width of the image on the page with higher dpi yielding smaller, “denser” images -
In R-generated figures higher
dpiwill yield larger images generally. If you specify a number that maxes out the image size on the page then a larger dpi will result in no visual change, but the image will be higher resolution and thus a bigger file. In general the formula for calculating the width of the figure using the dpi argument is(width in pixels) * (dpi/96dpi).
```{r dpi = 72}
ggplot(cars, aes(speed, dist)) + geom_point()
```
```{r dpi = 200}
# Higher dpi
ggplot(cars, aes(speed, dist)) + geom_point()
```
```{r dpi = 72}
include_graphics(img1_path)
```

```{r dpi = 200}
include_graphics(img1_path)
```

The fig.retina argument is a resolution multiplier
- Default is
fig.retina = 2
A retina display is a screen developed by Apple with a significantly higher pixel density than previous models. Prior to the release of retina displays most web images were optimized at 72dpi or 96dpi. On a high pixel density device these images will be displayed as either a smaller image (though still crisp looking) or at the original dimensions (and potentially fuzzy) – this site has a nice discussion. In order to avoid these display issues and create images that look good on all screens you may want to increase the resolution of your images. This can be done with the dpi argument directly or you can make use of the fig.retina argument.
The fig.retina argument is a dpi multiplier for displaying HTML output on retina screens and changes the chunk option dpi to dpi * fig.retina. If you are worried about your images displaying properly on retina screens you can leave the default as fig.retina = 2 – this will ensure crisp display on retina screens but be aware that it will double the physical size of your images. If you don't want this to happen you should set fig.retina = 1.
How images are affected in our HTML document when using fig.retina:
- External images: Since external images already exist and resolution cannot be increased, setting fig.retina = 2 results in an image on the page that is ½ of the original (creating a smaller but denser image).
- R-generated figures will appear on the page as being the same size, but figures with no explicit
fig.retinasetting will use the defaultfig.retina = 2setting. These figures will be twice as dense and thus twice the physical size as figures withfig.retina = 1orfig.retina = NULL. In the case of our ggplot, usingfig.retina = 1orfig.retina = NULLresults in an image that is 25kb while not specifyingfig.retinaor using the defaultfig.retina = 2results in a file that is 60kb.
```{r}
# No fig.retina (using default fig.retina = 2)
include_graphics(img1_path)
```

```{r, fig.retina = 1}
include_graphics(img1_path)
```

```{r}
# No fig.retina (using default fig.retina = 2)
# This file is 60kb
ggplot(cars, aes(speed, dist)) + geom_point()
```
```{r, fig.retina = 1}
# This file is 25kb
ggplot(cars, aes(speed, dist)) + geom_point()
```
Optimize R-generated images with optipng or pngquant
The knitr package includes “hooks” you can use to run functions before or after a code chunk to tweak the output. There are two pre-created hooks available in knitr that will optimize PNG images for web viewing: 1) hook_optipng and 2) hook_pngquant. The hook functions are available within knitr but before you can use either of them, you need to install the background programs on your machine.
Note that the built-in optimizers can only be used within R markdown on R-generated figures. To optimize external images see below.
To optimize your images you need to download optipng or pngquant. On a Windows machine you can download the zip files and you'll need to add the location of the programs to your PATH. On a Mac you can use homebrew to install using:
brew install optipng
brew install pngquant
To use either hook you have two steps, first you add the hook in a chunk with knit_hooks$set and then you optimize a specific image by setting the optipng or pngquant argument within the R chunk. (You could also set optipng or pngquant to run on all R generated images by setting a global chunk option with opts_chunk$set()).
- For optipng: the level of optimization is specified with
optipng = '-oX'whereXis a number 0-7 with 7 being the maximum optimization. Note that using-o7can result in additional processing time. - For pngquant: there is a speed/quality tradeoff parameter with
pngquant = --speed=1being the smallest file size. There are other arguments discussed on the wesite.
In our ggplot example, you can see below that the figures without optipng and with maximum optimization look identical, but optipng reduces the file size from 60kb to 17kb, a 3x size reduction. You can use optipng with a self-contained or non-self-contained HTML document.
```{r}
# Set up the hook
library(knitr)
knit_hooks$set(optipng = hook_optipng)
knit_hooks$set(pngquant = hook_pngquant)
```
```{r}
# No optimization, size is 60kb
ggplot(cars, aes(speed, dist)) + geom_point()
```
```{r, optipng = '-o7'}
# Maximum optimization, size is 17kb
ggplot(cars, aes(speed, dist)) + geom_point()
```
If you have external files you have two options: 1) you can use optipng or pngquant outside of R markdown. Using a terminal cd into your folder of images and run the programs. For our sloth images optipng does not result in significantly smaller images but pngquant reduces the files to approximately 1/3 their original size.
optipng -o7 *.png
pngquant --speed=1 *.png
Alternatively, you can write your own hook to optimize images in a folder. An example provided by the knitr creator Yihui Xie:
optipng = function(dir = '.') {
files = list.files(dir, '[.]png$', recursive = TRUE, full.names = TRUE)
for (f in files) system2('optipng', shQuote(f))
}
Bonus knitr and R markdown functionality
More functionality from include_graphics
Add multiple images at a time
The path argument in include_graphics will accept a vector of names. If you have a folder of images and want to add all them to your document at the same time simply point to the folder and voila!
```{r, echo = TRUE, out.width="30%"}
myimages<-list.files("images/", pattern = ".png", full.names = TRUE)
include_graphics(myimages)
```


Load an image from a URL
Super easy – point to an image on the web.
```{r, out.width = "50%"}
include_graphics("https://upload.wikimedia.org/wikipedia/commons/thumb/2/2e/MC_Drei-Finger-Faultier.jpg/330px-MC_Drei-Finger-Faultier.jpg")
```
Additional methods for adding images
We touched on this in an earlier section – here are two additional methods for adding images to your R Markdown document.
Adding images using markdown directly
Here we use markdown syntax to include an image.


Adding images using HTML
Here we use raw HTML to include an image
<img src="images/sloth1.png" alt="Upside-down sloths are so cute", width = "40%">

Style your image environment with CSS
You can use CSS to arrange your images, center them, add backgrounds etc. There are several ways to do this depending on whether your changes are quick and local or you want them to apply more broadly.
Use out.extra to apply CSS styles
The knitr package provide the out.extra argument to apply styles to a single chunk.
```{r, out.width="50%", out.extra='style="background-color: #9ecff7; padding:10px; display: inline-block;"', eval=FALSE}
plot(10:1, pch=21, bg="red")
```
Add CSS class to R chunk to apply styles
If you want to make significant styling changes you can create your CSS and include your styles either in your R markdown document directly by including the CSS between style tags like this:
<style>
.blue-outline {
background-color: #9ecff7;
padding:10px;
display: inline-block;
}
</style>
or you can include your styles in a separate docucument and reference this file in the YAML at the top. So, for example, you might create a style.css file and then at the top of your R markdown document you would include:
---
title: Tips and tricks for working with images and figures in R Markdown documents
output:
html_document:
css: style.css
---
Then to add a class directly to a single chunk, you can create a new hook that adds the opening HTML tags before and then closing tags after. So here we create a hook that allows us to feed our class to a class argument in the chunk.
knitr::knit_hooks$set(class = function(before, options, envir) {
if(before){
sprintf("<div class = '%s'>", options$class)
}else{
"</div>"
}
})
Then in your chunk, you can use your new hook like this:
```{r, class = "blue-outline"}
plot(1:10, pch=21, bg="blue")
```

Summary
R Markdown provides an useful framework for including images and figures in reproducible reports. But getting the image sizes and resolutions set correctly can be a challenge. Key considerations include:
- User-generated images and R-generated figures are handled differently. Not all of the same arguments can be applied to both types.
- Default settings are taken from both the
rmarkdownandknitrpackages. If a setting exists in both it will use thermarkdowndefault. - The
dpiargument is mostly not relevant for HTML output (though see thefig.retinaargument) - The
fig.widthandfig.heightarguments only apply to R-generated images, not to external images - The easiest way to change width is probably the
out.widthargument which applies to both R-generated and external images. - To ensure proper display on retina screens you can use the default
fig.retina = 2(or leave this argument blank as it is the default) but beware this will double the physical size of your images potentially leading to slower page loading. - The viewable size of external images can be changed with, for example, the
out.widthargument, but the actually physical size of the image will not change. So if you have a 5 MB image it will be 5 MB in your report even if it is 1 inch wide. - If the size of the HTML document matters to you, keep an eye on your figure sizes by checking the 'figure-html' folder that is associated with your report.
- You can use optipng to help optimize image size. You can apply optipng to R-generated images from within R markdown and apply optipng to external images from the command line.
Great coverage. have you tried with leaflet. At first blush cannot manipulate dimensions
Hi Andrew –
Thanks for your comment and good question! Yes the dimensions of a leaflet map can be controlled using both the out.width/out.height and fig.width/fig.height settings. If no width/height setting is applied to the R chunk the map will assume the default dimensions of 7in (width) by 5in (height).
Hopefully that answers your question.
Thanks!
GOLD!!!!
Many thanks for the post. Clarified all about images on Shiny / flexdashboard to me.
Great post! As a quite advanced Shiny / Rmarkdown user I found it quite refreshing.
And class argument in the chunk function is a life saver for me! Many thanks!
I found the “Use out.extra to apply CSS styles” method for adding a line around an image really helpful. I tried the method but it didn’t work for me. I didn’t get an error but no line was added either. I didn’t spend much time looking into it though. Thanks for a great resource!
I thought maybe this was because there had been updates to R markdown/knitr, but I just tried again and it works for me. Are you using eval = TRUE (I see that I have eval = FALSE).
Thanks for you clear presentation of all of this.
I’m also not able to get the line to appear around my image. The rest of your suggestions have worked great. I’m outputing to PDF but also trying html and it didn’t work there either.
“`{r tutor, out.width = “40%”, out.align = “right”, fig.extra=’style=”background-color: #9ecff7; padding:10px; display: inline-block;”‘, eval=TRUE}
include_graphics(“AC-Ciscka Tutoring at Dr Oscar-Loya.JPG”)
“`
Thank you very much for the article, it’ great help at flexdashboard with images.
Tamás
When I run knitr to make a word document, it also outputs all the graphs in another folder. It labels them 1 to 138. Is there a way to have knitr label them as their site ID? I can get the site ID to show up in the title for the graph, but I’m not sure how I can get that imported to the file name for the graph.
Thanks!
Are your R chunks named? I think that will do what you want (but not positive). Let me know.
Thanks Hollie! Hope to see you again at the rstudio conference. 🙂
Thanks Will! Yes, looking forward to it 🙂
you are truly a just right webmaster. The site loading speed is amazing.
It kind of feels that you are doing any unique trick.
Furthermore, The contents are masterwork. you’ve performed a fantastic task on this topic!