Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .Rbuildignore
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
^.appveyor.yml$
^.travis.yml$
^\.air.toml$
^\.claude$
^\.editorconfig$
^\.pre-commit-config.yaml$
^cran-comments.md$
^LICENSE.md$
^README.Rmd$
^README.html$
^Rakefile$
^pkgdown$
^_pkgdown.yml$
^raw-data$
^data-raw$
^codecov.yml$
^doc$
^tmp$
^Meta$
^\.git$
^\.github$
6 changes: 6 additions & 0 deletions .air.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[format]
indent-style = "tab"
indent-width = 4
line-ending = "lf"
line-width = 100
skip = ["tribble"]
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# https://editorconfig.org/
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.{r,R}]
indent_style = tab
21 changes: 21 additions & 0 deletions .github/workflows/air-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
on:
push:
branches: [main, master]
pull_request:

name: format-check

permissions: read-all

jobs:
format-check:
name: format-check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install
uses: posit-dev/setup-air@v1

- name: Check
run: air format . --check
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
README.html
codecov.yml
*.swp
/.claude/
/doc/
/Meta/
/pkgdown/
/tmp/
raw-data/*.png
data-raw/*.png
vignettes/*.R
vignettes/*.html
8 changes: 8 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
repos:
- repo: local
hooks:
- id: air
name: Format R code with air
entry: air format
language: system
files: \.R$|\.r$
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: gridpattern
Type: Package
Title: 'grid' Pattern Grobs
Version: 1.3.3-1
Version: 1.3.3-2
Authors@R: c(
person("Trevor L.", "Davis", role=c("aut", "cre"), email="trevor.l.davis@gmail.com",
comment = c(ORCID = "0000-0001-6341-4639")),
Expand All @@ -13,7 +13,7 @@ BugReports: https://github.com/trevorld/gridpattern/issues
License: MIT + file LICENSE
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.1
RoxygenNote: 7.3.3
Depends:
R (>= 3.4.0)
Imports:
Expand Down
265 changes: 144 additions & 121 deletions R/alphaMaskGrob.R
Original file line number Diff line number Diff line change
Expand Up @@ -47,150 +47,173 @@
#' }
#' }
#' @export
alphaMaskGrob <- function(maskee, masker,
use_R4.1_masks = getOption("ggpattern_use_R4.1_masks",
getOption("ggpattern_use_R4.1_features")),
png_device = NULL, res = getOption("ggpattern_res", 72),
name = NULL, gp = gpar(), vp = NULL) {
gTree(maskee = maskee, masker = masker,
use_R4.1_masks = use_R4.1_masks,
res = res, png_device = png_device,
name = name, gp = gp, vp = vp, cl = "alpha_mask")
alphaMaskGrob <- function(
maskee,
masker,
use_R4.1_masks = getOption(
"ggpattern_use_R4.1_masks",
getOption("ggpattern_use_R4.1_features")
),
png_device = NULL,
res = getOption("ggpattern_res", 72),
name = NULL,
gp = gpar(),
vp = NULL
) {
gTree(
maskee = maskee,
masker = masker,
use_R4.1_masks = use_R4.1_masks,
res = res,
png_device = png_device,
name = name,
gp = gp,
vp = vp,
cl = "alpha_mask"
)
}

# Avoid R CMD check WARNING on R 4.0 which lacks `mask` argument
vport <- function(...) viewport(...)

#' @export
makeContent.alpha_mask <- function(x) {
current_dev <- grDevices::dev.cur()
on.exit(grDevices::dev.set(current_dev))

use_R4.1_masks <- x$use_R4.1_masks
if (is.null(use_R4.1_masks))
use_R4.1_masks <- guess_has_R4.1_features("masks")
else
use_R4.1_masks <- as.logical(use_R4.1_masks)

stopifnot(getRversion() >= '4.1.0' || !use_R4.1_masks)

if (use_R4.1_masks) {
grob <- grobTree(x$maskee,
vp = vport(mask = x$masker),
name = "alpha_mask")
} else if (is.null(x$png_device) &&
getRversion() >= '4.1.0' &&
requireNamespace("ragg", quietly = TRUE) &&
packageVersion("ragg") >= '1.2.0') {
grob <- gridpattern_mask_agg_capture(x$maskee, x$masker, x$res)
} else {
png_device <- x$png_device %||% default_png_device()
if (device_supports_masks(png_device)) {
grob <- gridpattern_mask_raster_straight(x$maskee, x$masker, x$res, png_device)
} else {
grob <- gridpattern_mask_raster_manual(x$maskee, x$masker, x$res, png_device)
}
}

gl <- gList(grob)
setChildren(x, gl)
current_dev <- grDevices::dev.cur()
on.exit(grDevices::dev.set(current_dev))

use_R4.1_masks <- x$use_R4.1_masks
if (is.null(use_R4.1_masks)) {
use_R4.1_masks <- guess_has_R4.1_features("masks")
} else {
use_R4.1_masks <- as.logical(use_R4.1_masks)
}

stopifnot(getRversion() >= '4.1.0' || !use_R4.1_masks)

if (use_R4.1_masks) {
grob <- grobTree(x$maskee, vp = vport(mask = x$masker), name = "alpha_mask")
} else if (
is.null(x$png_device) &&
getRversion() >= '4.1.0' &&
requireNamespace("ragg", quietly = TRUE) &&
packageVersion("ragg") >= '1.2.0'
) {
grob <- gridpattern_mask_agg_capture(x$maskee, x$masker, x$res)
} else {
png_device <- x$png_device %||% default_png_device()
if (device_supports_masks(png_device)) {
grob <- gridpattern_mask_raster_straight(x$maskee, x$masker, x$res, png_device)
} else {
grob <- gridpattern_mask_raster_manual(x$maskee, x$masker, x$res, png_device)
}
}

gl <- gList(grob)
setChildren(x, gl)
}

device_supports_masks <- function(png_device) {
current_dev <- grDevices::dev.cur()
if (current_dev > 1) on.exit(grDevices::dev.set(current_dev))
png_file <- tempfile(fileext = ".png")
on.exit(unlink(png_file), add = TRUE)
png_device(png_file)
value <- guess_has_R4.1_features("masks")
dev.off()
value
current_dev <- grDevices::dev.cur()
if (current_dev > 1) {
on.exit(grDevices::dev.set(current_dev))
}
png_file <- tempfile(fileext = ".png")
on.exit(unlink(png_file), add = TRUE)
png_device(png_file)
value <- guess_has_R4.1_features("masks")
dev.off()
value
}

gridpattern_mask_agg_capture <- function(maskee, masker, res) {
current_dev <- grDevices::dev.cur()
if (current_dev > 1) on.exit(grDevices::dev.set(current_dev))
height <- res * convertHeight(unit(1, "npc"), "in", valueOnly = TRUE)
width <- res * convertWidth(unit(1, "npc"), "in", valueOnly = TRUE)

ragg::agg_capture(height = height, width = width, res = res, bg = "transparent")
grob <- alphaMaskGrob(maskee, masker, use_R4.1_masks = TRUE)
grid.draw(grob)
raster_masked <- dev.capture(native = FALSE)
dev.off()
grid::rasterGrob(raster_masked)
current_dev <- grDevices::dev.cur()
if (current_dev > 1) {
on.exit(grDevices::dev.set(current_dev))
}
height <- res * convertHeight(unit(1, "npc"), "in", valueOnly = TRUE)
width <- res * convertWidth(unit(1, "npc"), "in", valueOnly = TRUE)

ragg::agg_capture(height = height, width = width, res = res, bg = "transparent")
grob <- alphaMaskGrob(maskee, masker, use_R4.1_masks = TRUE)
grid.draw(grob)
raster_masked <- dev.capture(native = FALSE)
dev.off()
grid::rasterGrob(raster_masked)
}

default_png_device <- function() {
if (requireNamespace("ragg", quietly = TRUE)) {
ragg::agg_png
} else {
stopifnot(capabilities("png"))
grDevices::png
}
if (requireNamespace("ragg", quietly = TRUE)) {
ragg::agg_png
} else {
stopifnot(capabilities("png"))
grDevices::png
}
}

gridpattern_mask_raster_straight <- function(maskee, masker, res, png_device) {
current_dev <- grDevices::dev.cur()
if (current_dev > 1) on.exit(grDevices::dev.set(current_dev))
height <- res * convertHeight(unit(1, "npc"), "in", valueOnly = TRUE)
width <- res * convertWidth(unit(1, "npc"), "in", valueOnly = TRUE)

png_masked <- tempfile(fileext = ".png")
on.exit(unlink(png_masked), add = TRUE)
png_device(png_masked, height = height, width = width,
res = res, bg = "transparent")
grob <- alphaMaskGrob(maskee, masker, use_R4.1_masks = TRUE)
grid.draw(grob)
dev.off()

raster_masked <- png::readPNG(png_masked, native = FALSE)
grid::rasterGrob(raster_masked)
current_dev <- grDevices::dev.cur()
if (current_dev > 1) {
on.exit(grDevices::dev.set(current_dev))
}
height <- res * convertHeight(unit(1, "npc"), "in", valueOnly = TRUE)
width <- res * convertWidth(unit(1, "npc"), "in", valueOnly = TRUE)

png_masked <- tempfile(fileext = ".png")
on.exit(unlink(png_masked), add = TRUE)
png_device(png_masked, height = height, width = width, res = res, bg = "transparent")
grob <- alphaMaskGrob(maskee, masker, use_R4.1_masks = TRUE)
grid.draw(grob)
dev.off()

raster_masked <- png::readPNG(png_masked, native = FALSE)
grid::rasterGrob(raster_masked)
}

gridpattern_mask_raster_manual <- function(maskee, masker, res, png_device) {
current_dev <- grDevices::dev.cur()
if (current_dev > 1) on.exit(grDevices::dev.set(current_dev))
height <- res * convertHeight(unit(1, "npc"), "in", valueOnly = TRUE)
width <- res * convertWidth(unit(1, "npc"), "in", valueOnly = TRUE)

png_maskee <- tempfile(fileext = ".png")
on.exit(unlink(png_maskee), add = TRUE)
png_device(png_maskee, height = height, width = width,
res = res, bg = "transparent")
grid.draw(maskee)
dev.off()

png_masker <- tempfile(fileext = ".png")
on.exit(unlink(png_masker), add = TRUE)
png_device(png_masker, height = height, width = width,
res = res, bg = "transparent")
grid.draw(masker)
dev.off()

raster_maskee <- png::readPNG(png_maskee, native = FALSE)
raster_masker <- png::readPNG(png_masker, native = FALSE)

stopifnot(length(dim(raster_maskee)) == 3L,
length(dim(raster_masker)) == 3L,
dim(raster_maskee)[3L] >= 3L,
dim(raster_masker)[3L] >= 3L)
if (dim(raster_maskee)[3L] < 4L) {
raster_maskee <- add_alpha_channel(raster_maskee)
}
if (dim(raster_masker)[3L] < 4L) {
raster_masker <- add_alpha_channel(raster_masker)
}

raster_masked <- raster_maskee
raster_masked[, , 4L] <- raster_maskee[, , 4L] * raster_masker[, , 4L]

rasterGrob(raster_masked, name = "alpha_mask")
current_dev <- grDevices::dev.cur()
if (current_dev > 1) {
on.exit(grDevices::dev.set(current_dev))
}
height <- res * convertHeight(unit(1, "npc"), "in", valueOnly = TRUE)
width <- res * convertWidth(unit(1, "npc"), "in", valueOnly = TRUE)

png_maskee <- tempfile(fileext = ".png")
on.exit(unlink(png_maskee), add = TRUE)
png_device(png_maskee, height = height, width = width, res = res, bg = "transparent")
grid.draw(maskee)
dev.off()

png_masker <- tempfile(fileext = ".png")
on.exit(unlink(png_masker), add = TRUE)
png_device(png_masker, height = height, width = width, res = res, bg = "transparent")
grid.draw(masker)
dev.off()

raster_maskee <- png::readPNG(png_maskee, native = FALSE)
raster_masker <- png::readPNG(png_masker, native = FALSE)

stopifnot(
length(dim(raster_maskee)) == 3L,
length(dim(raster_masker)) == 3L,
dim(raster_maskee)[3L] >= 3L,
dim(raster_masker)[3L] >= 3L
)
if (dim(raster_maskee)[3L] < 4L) {
raster_maskee <- add_alpha_channel(raster_maskee)
}
if (dim(raster_masker)[3L] < 4L) {
raster_masker <- add_alpha_channel(raster_masker)
}

raster_masked <- raster_maskee
raster_masked[,, 4L] <- raster_maskee[,, 4L] * raster_masker[,, 4L]

rasterGrob(raster_masked, name = "alpha_mask")
}

add_alpha_channel <- function(a) {
a_ <- array(NA, dim = c(dim(a)[1], dim(a)[2], 4L))
a_[, , -4L] <- a
a_[, , 4L] <- 1.
a_
a_ <- array(NA, dim = c(dim(a)[1], dim(a)[2], 4L))
a_[,, -4L] <- a
a_[,, 4L] <- 1.
a_
}
Loading
Loading