#' Scan a file or image for QR codes.
#'
#' \code{qr_scan} reads in an image file or \pkg{magick} image object and first
#' tries to read it using the C++ engine, which is fast and robust at locating
#' the alignment patterns. However, it is less robust at decoding. If no codes
#' can be read, the image and corners are passed to the JS engine, which is
#' slower but has a higher success rate on cropped images.
#'
#' This function calls \code{\link{qr_scan_cpp}} and possibly
#' \code{\link{qr_scan_js_from_corners}}. Each function has a
#' double-\code{while} loop that progressively pushes mid-brightness pixels to
#' pure black, and if that fails, progressively pushes mid-brightness pixels to
#' pure white. This algorithm was developed for identifying QR codes on white
#' printed sheets in outdoor images, in bright sun with or without shadows. To
#' speed up scanning, you can use arguments \code{lighten = F, darken = F} which
#' will skip any thresholding. If you use both \code{lighten = T, darken = T},
#' scanning may be quite slow until a decodable QR code is found. In those cases,
#' a progress bar will attempt to be shown, if you have the \pkg{progress}
#' package (\url{https://github.com/r-lib/progress}) available on your machine.
#'
#' To BYO algorithm, you can use those two functions as templates. For example,
#' \code{\link{image_morphology}} with \code{(..., morphology = "Open", kernel =
#' "Square:n")} (varying \code{n} from 2 to 10) may repair corrupted QR blocks.
#'
#'
#' @param image A path to a \pkg{magick}-readable file, e.g. jpg or png, or a \pkg{magick} object.
#' @param flop Logical. Should the image be mirrored L-R before reading?
#' @param lighten Logical. Should under-exposed areas of the image be lightened to increase contrast? Useful for images in shadow. Default \code{FALSE}.
#' @param darken Logical. Should over-exposed areas of the image be darkened to increase contrast? Useful for images in bright light. Default \code{TRUE}.
#' @param plot Logical. Should the image with any detected codes be shown after reading? (Requires ggplot2)
#' @param force_js Logical. Should the JS library run even if the C++ library is able to read the code?
#' @param no_js Logical. Never use the JS library, even if no QR codes are decoded.
#' @param verbose Logical. Should warnings print for potentially slow operations?
#' @return A list of dataframes, \strong{values} and \strong{points}, each with a column \strong{id}.
qr_scan <- function(
image,
flop = FALSE,
lighten = FALSE,
darken = TRUE,
plot = FALSE,
force_js = FALSE,
no_js = FALSE,
verbose = interactive()
) {
if (is.character(image)) {
mgk <- magick::image_read(image)
} else if ("magick-image" %in% class(image)) {
mgk <- image
} else {
stop("Supply either an image file path or a magick image object.")
}
if (flop) {
mgk <- magick::image_flop(mgk)
}
code_obj <- qr_scan_cpp(mgk, flop = FALSE, lighten = lighten, darken = darken, verbose = verbose)
code_pts <- code_obj$points
# TODO image_destroy(mgk)
if (isTRUE(nrow(code_pts) > 0) && any(code_obj$values$value == "") || force_js) {
if (!no_js && qr_v8_checker_()) {
code_list <- split(code_pts, code_pts$id)
code_obj <-
purrr::map(
code_list,
~qr_scan_js_from_corners(mgk, .x, lighten = lighten, darken = darken, verbose = F)
) %>%
qr_parse_js_()
}
}
if (plot && requireNamespace("ggplot2", quietly = TRUE)) {
print(qr_plot(mgk, code_obj))
}
cat("\n")
return(code_obj)
}
#' Plot a \pkg{magick} image with any detected QR codes.
#'
#' \code{qr_plot} is a helper called inside \code{qr_scan} to visualize images
#' and any QR codes. Requires \code{ggplot2}.
#'
#' @param mgk The image object that was scanned.
#' @param code_obj A list of dataframes, \strong{values} and \strong{points}, generated by \code{qr_scan}.
#' @return A ggplot object.
qr_plot <- function(mgk, code_obj) {
if (!requireNamespace("ggplot2", quietly = TRUE)) {
stop("Plotting decoded QR images requires ggplot2.")
}
dat <- merge(code_obj$values, code_obj$points, all = T)
# slow to resize large images, but slow to render them as well, how to fix?
if (magick::image_info(mgk)$width > 1000) {
mgk <- magick::image_resize(mgk, "50%")
dat$x <- dat$x/2
dat$y <- dat$y/2
}
if (nrow(dat) == 0) {
magick::image_ggplot(mgk)
} else {
centers <- stats::aggregate(
cbind(x, y) ~ value+id,
data = dat,
FUN = mean
)
magick::image_ggplot(mgk) +
ggplot2::geom_point(
data = dat,
ggplot2::aes(x, y, color = as.character(id)),
show.legend = FALSE, size = 4
) +
ggplot2::geom_label(
data = centers,
ggplot2::aes(x, y, label = value),
fontface = "bold"
)
}
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.