#' Generate the QR code
#'
#' A [QR code](https://en.wikipedia.org/wiki/QR_code) is a two-dimensional
#' barcode developed by the Denso Wave company.
#' @param x the input string
#' @param ecl the required error correction level.
#' Available options are `"L"` (7%), `"M"` (15%), `"Q"` (25%) and `"H"` (30%).
#' Defaults to `"L"`.
#' @return The QR code as a logical matrix with "qr_code" class.
#' @examples
#' qr_code("https://www.r-project.org")
#' qr <- qr_code("https://cran.r-project.org/package=qrcode", ecl = "M")
#' qr
#' plot(qr)
#' # the qr_code object is a logical matrix
#' str(qr)
#' qr[1:10, 1:10]
#' @export
#' @author Thierry Onkelinx
#' @family qr
qr_code <- function(x, ecl = c("L", "M", "Q", "H")) {
ecl <- match.arg(ecl)
base_matrix <- qr_matrix(x = x, ecl = ecl)
mask_patterns <- mask_pattern(code_dim = ncol(base_matrix$patterns))
candidate <- vapply(
0:7, FUN.VALUE = vector("list", 1), base_matrix = base_matrix,
FUN = function(mask, base_matrix) {
list(
apply_mask(
payload = base_matrix$payload, patterns = base_matrix$patterns,
mask = mask, version = base_matrix$version,
mask_pattern = mask_patterns, ecl = ecl
)
)
}
)
est_penalty <- vapply(candidate, penalty, numeric(1))
# select the lowest penalty
best <- candidate[[head(order(est_penalty), 1)]]
# add quiet zone
best <- rbind(FALSE, FALSE, FALSE, best, FALSE, FALSE, FALSE)
best <- cbind(FALSE, FALSE, FALSE, best, FALSE, FALSE, FALSE)
class(best) <- c("qr_code", "matrix")
attr(best, "string") <- x
attr(best, "ecl") <- ecl
return(best)
}
mask_pattern <- function(code_dim, mask = 0:7) {
coords <- expand.grid(x = seq_len(code_dim), y = seq_len(code_dim))
# take into account that the rules use indices starting at 0
# while R uses indices starting at 1
coords$mask_0 <- (coords$x + coords$y) %% 2 == 0
coords$mask_1 <- coords$x %% 2 == 1
coords$mask_2 <- coords$y %% 3 == 1
coords$mask_3 <- (coords$x + coords$y) %% 3 == 2
coords$mask_4 <- (
floor((coords$x - 1) / 2) + floor((coords$y - 1) / 3)
) %% 2 == 0
coords$mask_5 <- (
((coords$x - 1) * (coords$y - 1)) %% 2 +
((coords$x - 1) * (coords$y - 1)) %% 3
) == 0
coords$mask_6 <- (
(((coords$x - 1) * (coords$y - 1)) %% 2 +
((coords$x - 1) * (coords$y - 1)) %% 3) %% 2
) == 0
coords$mask_7 <- (
(((coords$x - 1) + (coords$y - 1)) %% 2 +
((coords$x - 1) * (coords$y - 1)) %% 3) %% 2
) == 0
return(coords)
}
#' @importFrom utils tail
format_string <- function(ecl, mask) {
ecl_int <- as.integer(factor(ecl, levels = c("M", "L", "H", "Q"))) - 1
version_bits <- int2bits(ecl_int * 8 + mask, n_bit = 5L)
remainder <- c(version_bits, rep(FALSE, 10))
generator <- int2bits(1335, n_bit = 11)
while (!remainder[1] && length(remainder) > 10) {
remainder <- tail(remainder, -1)
}
while (length(remainder) > 10) {
divider <- c(generator, rep(FALSE, length(remainder) - length(generator)))
remainder <- xor(remainder, divider)
while (!remainder[1]) {
remainder <- tail(remainder, -1)
}
}
bit_string <- c(version_bits, rep(FALSE, 10 - length(remainder)), remainder)
generator <- int2bits(21522, n_bit = 15L)
bits(xor(bit_string, generator))
}
#' @importFrom utils tail
format_version <- function(version) {
version_bits <- int2bits(version, 6)
remainder <- c(version_bits, rep(FALSE, 12))
generator <- int2bits(7973, 13)
while (!remainder[1] && length(remainder) > 12) {
remainder <- tail(remainder, -1)
}
while (length(remainder) > 12) {
divider <- c(generator, rep(FALSE, length(remainder) - length(generator)))
remainder <- xor(remainder, divider)
while (!remainder[1]) {
remainder <- tail(remainder, -1)
}
}
c(version_bits, rep(FALSE, 12 - length(remainder)), remainder)
}
#' @importFrom assertthat assert_that is.number
apply_mask <- function(payload, patterns, mask, version, mask_pattern, ecl) {
assert_that(is.number(mask), 0 <= mask, mask <= 7)
mask_coord <- as.matrix(
mask_pattern[mask_pattern[[sprintf("mask_%i", mask)]], c("x", "y")]
)
payload[mask_coord] <- !payload[mask_coord]
payload[!is.na(patterns)] <- FALSE
payload <- payload | patterns
payload[is.na(payload)] <- FALSE
fs <- format_string(ecl, mask)
payload[9, 1:6] <- head(fs, 6)
payload[9, 8:9] <- fs[7:8]
payload[9, ncol(payload) - 7:0] <- tail(fs, 8)
payload[ncol(payload) - 0:6, 9] <- head(fs, 7)
payload[8, 9] <- fs[9]
payload[6:1, 9] <- tail(fs, 6)
if (version < 6) {
return(payload)
}
fs <- format_version(version)
fs <- matrix(rev(fs), nrow = 3)
payload[ncol(payload) - 10:8, 1:6] <- fs
payload[1:6, ncol(payload) - 10:8] <- t(fs)
return(payload)
}
penalty <- function(payload) {
penalty_1(payload) + penalty_2(payload) + penalty_3(payload) +
penalty_4(payload)
}
penalty_1 <- function(payload) {
# each group of five or more same-coloured modules in a row (or column)
# 3 points per group + 1 additional point per modules above 5
delta <- apply(payload, 1, diff)
group_id <- rbind(0, apply(abs(delta), 2, cumsum))
groups <- unlist(apply(group_id, 2, table))
groups <- groups[groups >= 5] - 5
horizontal <- sum(groups) + 3 * length(groups)
# vertical
delta <- apply(payload, 2, diff)
group_id <- rbind(0, apply(abs(delta), 2, cumsum))
groups <- unlist(apply(group_id, 2, table))
groups <- groups[groups >= 5] - 5
vertical <- sum(groups) + 3 * length(groups)
return(horizontal + vertical)
}
penalty_2 <- function(payload) {
# 3 times the number of 2x2 modules with the same colour
horizontal <- payload[-ncol(payload), -ncol(payload)] ==
payload[-ncol(payload), -1]
vertical <- payload[-ncol(payload), -ncol(payload)] ==
payload[-1, -ncol(payload)]
diagonal <- payload[-ncol(payload), -ncol(payload)] == payload[-1, -1]
3 * sum(horizontal & vertical & diagonal)
}
penalty_3 <- function(payload) {
# pattern similar to finder pattern
# 40 points per item
payload_int <- apply(payload, 1, as.integer)
payload_char <- apply(payload_int, 2, as.character)
horizontal <- apply(payload_char, 2, paste, collapse = "")
vertical <- apply(payload_char, 1, paste, collapse = "")
40 * (sum(grepl("10111010000", c(horizontal, vertical))) +
sum(grepl("00001011101", c(horizontal, vertical))))
}
penalty_4 <- function(payload) {
# average darkness
10 * floor(abs(mean(payload) - 0.5) * 20)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.