Skip to content

Commit f25a19d

Browse files
authored
Update image generation functions (#803)
* Add base64enc to Imports * Use `base64enc::base64encode()` in `get_image_uri()1 * Refactor URI code * Add testthat snapshot testing * Delete test_image_png_uri.txt * Remove CID generation statements * Allow for multiple image URI generation * Merge test files/snapshots * Update help files using roxygen * Add testthat skip declarations * Reorganize functions in different files * Refactor `test_image()` * Rewrite `ggplot_image()` so it can accept >1 plots * Add several testthat tests * Update testthat tests * Update test-image.R * Make changes based on code review. * Update test-image.R * Create .gitattributes * Update .Rbuildignore * Remove `skip_on_os("windows")` directive * Remove `skip_on_os("windows")` directive
1 parent 1729b01 commit f25a19d

File tree

14 files changed

+251
-215
lines changed

14 files changed

+251
-215
lines changed

.Rbuildignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
^pkgdown$
44
^scripts$
55
^\.github$
6+
^\.gitattributes$
67
^\.Rproj\.user$
78
^gt\.Rproj$
89
^_pkgdown\.yml$

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
inst/graphics/test_image.svg -text

DESCRIPTION

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ ByteCompile: true
2626
RoxygenNote: 7.1.1
2727
Depends:
2828
R (>= 3.2.0)
29-
Imports:
29+
Imports:
30+
base64enc (>= 0.1-3),
3031
bitops (>= 1.0.6),
3132
checkmate (>= 2.0.0),
3233
commonmark (>= 1.7),
@@ -57,7 +58,6 @@ Suggests:
5758
Roxygen: list(markdown = TRUE)
5859
Collate:
5960
'as_data_frame.R'
60-
'base64.R'
6161
'build_data.R'
6262
'compile_scss.R'
6363
'data_color.R'

R/base64.R

Lines changed: 0 additions & 75 deletions
This file was deleted.

R/image.R

Lines changed: 75 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
#' [html()] helper function.
1313
#'
1414
#' By itself, the function creates an HTML image tag, so, the call
15-
#' `web_image("http://some.web.site/image.png")` evaluates to:
15+
#' `web_image("http://example.com/image.png")` evaluates to:
1616
#'
17-
#' `<img src=\"http://some.web.site/image.png\" style=\"height:30px;\">`
17+
#' `<img src=\"http://example.com/image.png\" style=\"height:30px;\">`
1818
#'
1919
#' where a height of `30px` is a default height chosen to work well within the
2020
#' heights of most table rows.
@@ -106,8 +106,7 @@ web_image <- function(url,
106106
height <- paste0(height, "px")
107107
}
108108

109-
glue::glue("<img src=\"{url}\" style=\"height:{height};\">") %>%
110-
as.character()
109+
paste0("<img src=\"", url, "\" style=\"height:", height, ";\">")
111110
}
112111

113112
#' Helper function for adding a local image
@@ -128,7 +127,7 @@ web_image <- function(url,
128127
#' available in the **gt** package using the [test_image()] function. Using
129128
#' that, the call `local_image(file = test_image(type = "png"))` evaluates to:
130129
#'
131-
#' `<img cid=<random CID> src=<data URI> style=\"height:30px;\">`
130+
#' `<img src=<data URI> style=\"height:30px;\">`
132131
#'
133132
#' where a height of `30px` is a default height chosen to work well within the
134133
#' heights of most table rows.
@@ -175,27 +174,17 @@ local_image <- function(filename,
175174
height = 30) {
176175

177176
# Normalize file path
178-
filename <- filename %>% path_expand()
177+
filename <- path_expand(filename)
179178

180179
if (is.numeric(height)) {
181180
height <- paste0(height, "px")
182181
}
183182

184-
# Construct a CID based on the filename
185-
# with a random string prepended to it
186-
cid <-
187-
paste0(
188-
sample(letters, 12) %>% paste(collapse = ""), "__",
189-
basename(filename)
190-
)
191-
192183
# Create the image URI
193184
uri <- get_image_uri(filename)
194185

195-
# Generate the Base64-encoded image and place it
196-
# within <img> tags
197-
glue::glue("<img cid=\"{cid}\" src=\"{uri}\" style=\"height:{height};\">") %>%
198-
as.character()
186+
# Generate the Base64-encoded image and place it within <img> tags
187+
paste0("<img src=\"", uri, "\" style=\"height:", height, ";\">")
199188
}
200189

201190
#' Helper function for adding a ggplot
@@ -216,7 +205,7 @@ local_image <- function(filename,
216205
#' object, and using it within `ggplot_image(plot_object = <plot object>`
217206
#' evaluates to:
218207
#'
219-
#' `<img cid=<random CID> src=<data URI> style=\"height:100px;\">`
208+
#' `<img src=<data URI> style=\"height:100px;\">`
220209
#'
221210
#' where a height of `100px` is a default height chosen to work well within the
222211
#' heights of most table rows. There is the option to modify the aspect ratio of
@@ -284,24 +273,35 @@ ggplot_image <- function(plot_object,
284273
height <- paste0(height, "px")
285274
}
286275

287-
# Save PNG file to disk
288-
ggplot2::ggsave(
289-
filename = "temp_ggplot.png",
290-
plot = plot_object,
291-
device = "png",
292-
dpi = 100,
293-
width = 5 * aspect_ratio,
294-
height = 5
295-
)
276+
# Upgrade `plot_object` to a list if only a single ggplot object is provided
277+
if (inherits(plot_object, "gg")) {
278+
plot_object <- list(plot_object)
279+
}
280+
281+
vapply(
282+
seq_along(plot_object),
283+
FUN.VALUE = character(1),
284+
USE.NAMES = FALSE,
285+
FUN = function(x) {
296286

297-
# Wait longer for file to be written on async filesystems
298-
Sys.sleep(1)
287+
filename <-
288+
paste0("temp_ggplot_", formatC(x, width = 4, flag = "0") , ".png")
299289

300-
image_html <- local_image(filename = "temp_ggplot.png", height = height)
290+
# Save PNG file to disk
291+
ggplot2::ggsave(
292+
filename = filename,
293+
plot = plot_object[[x]],
294+
device = "png",
295+
dpi = 100,
296+
width = 5 * aspect_ratio,
297+
height = 5
298+
)
301299

302-
file.remove("temp_ggplot.png")
300+
on.exit(file.remove(filename))
303301

304-
image_html
302+
local_image(filename = filename, height = height)
303+
}
304+
)
305305
}
306306

307307
#' Generate a path to a test image
@@ -325,9 +325,46 @@ test_image <- function(type = c("png", "svg")) {
325325

326326
type <- match.arg(type)
327327

328-
if (type == "png") {
329-
system_file(file = "graphics/test_image.png")
330-
} else {
331-
system_file(file = "graphics/test_image.svg")
332-
}
328+
system_file(file = paste0("graphics/test_image.", type))
329+
}
330+
331+
# Function for setting the MIME type
332+
get_mime_type <- function(file) {
333+
334+
extension <- tolower(get_file_ext(file))
335+
336+
switch(
337+
extension,
338+
svg = "image/svg+xml",
339+
jpg = "image/jpeg",
340+
paste("image", extension, sep = "/")
341+
)
342+
}
343+
344+
# Get image URIs from on-disk graphics files
345+
# as a vector Base64-encoded image strings
346+
get_image_uri <- function(file) {
347+
348+
# Create a list of `raw` objects
349+
image_raw <-
350+
lapply(
351+
file, FUN = function(x) {
352+
readBin(
353+
con = x,
354+
what = "raw",
355+
n = file.info(x)$size
356+
)
357+
}
358+
)
359+
360+
vapply(
361+
seq_along(image_raw),
362+
FUN.VALUE = character(1),
363+
USE.NAMES = FALSE, FUN = function(x) {
364+
paste0(
365+
"data:", get_mime_type(file[x]),
366+
";base64,", base64enc::base64encode(image_raw[[x]])
367+
)
368+
}
369+
)
333370
}

R/utils.R

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1179,13 +1179,26 @@ resolve_border_side <- function(side) {
11791179
all = "all")
11801180
}
11811181

1182-
#' Expand a path using fs::path_ex
1182+
#' Expand a path using fs::path_expand
1183+
#'
11831184
#' @noRd
11841185
path_expand <- function(file) {
11851186

11861187
fs::path_expand(file)
11871188
}
11881189

1190+
# TODO: the `get_file_ext()` function overlaps greatly with `gtsave_file_ext()`;
1191+
# both are not vectorized
1192+
1193+
#' Get a file's extension
1194+
#'
1195+
#' @noRd
1196+
get_file_ext <- function(file) {
1197+
1198+
pos <- regexpr("\\.([[:alnum:]]+)$", file)
1199+
ifelse(pos > -1L, substring(file, pos + 1L), "")
1200+
}
1201+
11891202
validate_marks <- function(marks) {
11901203

11911204
if (is.null(marks)) {

inst/graphics/test_image_png_uri.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

man/ggplot_image.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/local_image.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/web_image.Rd

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)