#' Draw rectangle filled with a texture image
#'
#' A texture grob is a grid grob that draws a rectangle filled with an image. The image will
#' be tiled if it is not large enough to fill the entire space of the rectangle.
#' @param img The image, in magick format
#' @param x Unit object specifying x position of rectangle
#' @param y Unit object specifying y position of rectangle
#' @param width Unit object specifying the rectangle width
#' @param height Unit object specifying the rectangle height
#' @param just Vector of two numeric values specifying horizontal and vertical
#' justification of rectangle relative to `x` and `y`.
#' @param img_width Unit object specifying the width of the texture image.
#' If `"null"` unit is used, then the image width is calculated relative
#' to the rectangle width.
#' @param img_height Unit object specifying the height of the texture image.
#' If `"null"` unit is used, then the image height is calculated relative
#' to the rectangle height.
#' @param nrow Number of image rows. If `NA`, is calculated automatically
#' from the available space.
#' @param ncol Number of image columns. If `NA`, is calculated automatically
#' from the available space.
#' @param hjust Horizontal justification of images.
#' @param vjust Vertical justification of images.
#' @param fill Fill color for rectangle.
#' @param color Border color for rectangle.
#' @param lty Line type for border.
#' @param lwd Line width for border.
#' @param clip Should images be clipped to rectangle extent? `"on"` means
#' yes, `"off"` means no.
#' @param repmax Maximum number of image repetitions. By default set to 500.
#' This is a safety check against bad parameter settings that might create
#' hundreds of thousands of images or more.
#' @param interpolate A logical value indicating whether to linearly interpolate the image
#' (the alternative is to use nearest-neighbour interpolation, which gives a more blocky result).
#' @examples
#' img <- magick::image_read("https://jeroen.github.io/images/Rlogo.png")
#'
#' grid.newpage()
#' tg1 <- texture_grob(
#' img,
#' x = unit(.2, "npc"), y = unit(.05, "npc"),
#' width = unit(.1, "npc"), height = unit(.9, "npc"),
#' img_width = unit(.5, "in"), ncol = 1
#' )
#' tg2 <- texture_grob(
#' img,
#' x = unit(.5, "npc"), y = unit(.05, "npc"),
#' width = unit(.3, "npc"), height = unit(.6, "npc"),
#' img_width = unit(.5, "in"), ncol = 1
#' )
#'
#' grid.draw(tg1)
#' grid.draw(tg2)
#' @export
texture_grob <- function(img,
x = unit(0.5, "npc"), y = unit(0.5, "npc"),
width = unit(1, "npc"), height = unit(1, "npc"),
just = c(0, 0),
img_width = unit(1, "null"), img_height = NA,
nrow = NA, ncol = NA, hjust = 0.5, vjust = 0,
fill = "#E8E8E8", color = "#000000", lty = 1,
lwd = 1, clip = "on", repmax = 500, interpolate = TRUE) {
if (is.null(img)) {
stop("Cannot create texture grob without valid image.", call. = FALSE)
}
# we use NA to indicate missing values, not NULL
img_width <- img_width %||% NA
img_height <- img_height %||% NA
nrow <- nrow %||% NA
ncol <- ncol %||% NA
if (is.na(img_width) && is.na(img_height)) {
stop("Must specify at least one of `img_width` and `img_height`.", call. = FALSE)
}
vp <- viewport(x, y, width, height, just = just, clip = clip)
# image aspect ratio
asp <- get_asp(img)
# prebuilding the image grob speeds things up for drawing
img_grob <- rasterGrob(img, interpolate = interpolate)
# the actual content is generated by the makeContent() function
g <- gTree(
img_grob = img_grob, img_width = img_width, img_height = img_height,
asp = asp, nrow = nrow, ncol = ncol, hjust = hjust, vjust = vjust,
fill = fill, color = color, lty = lty, lwd = lwd, repmax = repmax,
vp = vp, cl = "texture_grob"
)
}
#' @rdname texture_grob
#' @usage NULL
#' @export
makeContent.texture_grob <- function(x) {
grob_width_in <- convertWidth(unit(1, "npc"), "in", valueOnly = TRUE)
grob_height_in <- convertHeight(unit(1, "npc"), "in", valueOnly = TRUE)
grob_asp <- grob_height_in / grob_width_in
asp <- x$asp
img_grob <- x$img_grob
if (is.na(x$img_width)) {
# image width not specified
if (is_null_unit(x$img_height)) {
img_height_in <- grob_height_in * as.numeric(x$img_height)
} else {
img_height_in <- convertHeight(x$img_height, "in", valueOnly = TRUE)
}
img_width_in <- img_height_in / asp
} else if (is.na(x$img_height)) {
# image height not specified
if (is_null_unit(x$img_width)) {
img_width_in <- grob_width_in * as.numeric(x$img_width)
} else {
img_width_in <- convertHeight(x$img_width, "in", valueOnly = TRUE)
}
img_height_in <- img_width_in * asp
} else {
# both image width and image height specified
if (is_null_unit(x$img_height)) {
img_height_in <- grob_height_in * as.numeric(x$img_height)
} else {
img_height_in <- convertHeight(x$img_height, "in", valueOnly = TRUE)
}
if (is_null_unit(x$img_width)) {
img_width_in <- grob_width_in * as.numeric(x$img_width)
} else {
img_width_in <- convertHeight(x$img_width, "in", valueOnly = TRUE)
}
# when both width and height are specified we need to adjust the
# aspect ratio accordingly
asp <- img_height_in / img_width_in
img_grob <- editGrob(img_grob, width = unit(img_width_in, "in"), height = unit(img_height_in, "in"))
}
ncol <- x$ncol
if (is.na(ncol)) {
ncol <- ceiling(grob_width_in / img_width_in) # number of image columns we need
}
nrow <- x$nrow
if (is.na(nrow)) {
nrow <- ceiling(grob_height_in / img_height_in) # number of image rows we need
}
bg <- rectGrob(gp = gpar(fill = x$fill, col = NA))
fg <- rectGrob(gp = gpar(fill = NA, col = x$color, lty = x$lty, lwd = x$lwd))
if (nrow*ncol > x$repmax) {
warning("Number of tiling images exceeds maximum allowed. Verify your settings or increase `repmax`.", call. = FALSE)
children <- list(
textGrob("Too many\nimage tiles.\nCheck settings.")
)
} else {
children <- pmap(
expand.grid(row = 1:nrow, col = 1:ncol),
function(row, col) {
vp <- viewport(
x = unit((col - 1 - (ncol - 1)*x$hjust)*img_width_in + x$hjust*grob_width_in, "in"),
y = unit((row - 1 - (nrow - 1)*x$vjust)*img_height_in + x$vjust*grob_height_in, "in"),
width = unit(img_width_in, "in"), height = unit(img_height_in, "in"),
just = c(x$hjust, x$vjust),
name = "null"
)
rg <- img_grob
rg$vp <- vp
# need to change name, otherwise grid doesn't draw :-(
rg$name <- paste0("child.", row, ".", col)
rg
}
)
}
# add background to children
children <- c(list(bg), children, list(fg))
# convert to gList and set
children <- do.call(gList, children)
setChildren(x, children)
}
# calculate aspect ratio of a magick image
get_asp <- function(img) {
info <- magick::image_info(img)
info$height / info$width
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.