R 图形中的组、路径和蒙版



更新:(2023-05-18)合成运算符的行为已在 R 版本 4.3.0 中修改(影响“clear”和“source”运算符)。本文中的示例已更新,以便它们产生相同输出(只是使用不同的运算符)。

R 版本 4.1.0 中,为 R 图形引擎添加了对渐变填充、图案填充、剪裁路径和蒙版的支持。

R 的开发版本(可能成为 R 版本 4.2.0)包含对更多图形工具的支持:组、合成运算符和仿射变换,以及对路径和蒙版的一些调整。

已将这些新功能的 R 级接口添加到“grid”图形包中。

library(grid)

以下代码演示如何使用新 grid.group() 函数绘制组。基本思想是我们可以孤立地绘制一组形状,然后将结果添加到主图像中。在本例中,我们在将矩形和圆形添加到图像之前,将它们作为一个独立组绘制。

孤立地绘制组的一个优点是,我们可以使用不同的合成运算符组合形状。在本例中,我们使用“dest.out”运算符,这意味着矩形不会绘制在圆形之上,而是会在圆形中创建一个孔。

首先绘制了一条绿线,以表明圆形中有一个孔,我们可以通过这个孔看到绿线。

grid.segments(gp=gpar(col=3, lwd=50))
grid.group(rectGrob(width=.4, height=.2, gp=gpar(fill="black")),
           "dest.out",
           circleGrob(r=.4, gp=gpar(col=NA, fill=4)))

以下代码演示了新的路径绘制功能,其中包括用于填充路径的新函数 grid.fill()。可以从任意数量的形状创建路径,然后我们可以描边或填充路径(或同时进行)。在本例中,我们根据矩形和圆形描述路径。

当路径由重叠的形状组成时,路径的“内部”(即填充的区域)可能会变得复杂。我们可以控制用于决定填充区域的“规则”。在本例中,我们使用“even-odd”规则,这意味着矩形内的区域实际上在路径外部;结果仍然是在圆形中创建一个孔。

路径填充为蓝色(无边框)。

grid.segments(gp=gpar(col=3, lwd=50))
path <- gTree(children=gList(circleGrob(r=.4), 
                             rectGrob(width=.4, height=.2)))
grid.fill(path,
          rule="evenodd",
          gp=gpar(col=NA, fill=4))

以下代码演示了新的亮度蒙版支持,该支持可通过 as.mask() 函数获得。as.mask() 函数根据一个 grob 和一个 type 创建一个蒙版。

type 可以是 "luminance",这意味着 grob 的亮度决定了蒙版输出的半透明度。在本例中,我们根据一个白色圆形(上面绘制了一个黑色矩形)定义一个蒙版。

当我们使用亮度蒙版推送视口时,任何后续绘制都将在蒙版为白色时不透明,蒙版为黑色时透明(蒙版为灰色时半透明)。在这种情况下,在使用蒙版推送视口后,我们用蓝色填充整个图像,结果是一个蓝色圆圈(因为蒙版中的圆圈为白色),并有一个孔(因为蒙版中的矩形为黑色)。

pdf("luminance-mask.pdf", width=2, height=2)
grid.segments(gp=gpar(col=3, lwd=50))
mask <- gTree(children=gList(circleGrob(r=.4, 
                                        gp=gpar(col=NA, fill="white")), 
                             rectGrob(width=.4, height=.2,
                                      gp=gpar(col=NA, fill="black"))))
pushViewport(viewport(mask=as.mask(mask, "luminance")))
grid.rect(gp=gpar(fill=4))
dev.off()

example of a luminance mask

其余示例演示仿射变换,使用 grid.define() 函数和 grid.use() 函数。如果我们在一个视口中定义一个组(不绘制它),然后在另一个视口中使用该组,则该组将根据两个视口的相对位置、大小和旋转进行变换。在这种情况下,我们在一个与图像大小相同的视口中,基于一个圆圈和一个矩形(使用“dest.out”运算符,以便矩形在圆圈中创建一个孔)定义一个组,然后推送一个仅为图像高度三分之一的视口,并使用我们定义的组。

这会生成一个垂直压扁的组版本,因为我们使用该组的视口比定义该组的视口短得多。

grob <- groupGrob(rectGrob(width=.4, height=.2, gp=gpar(fill="black")),
                  "dest.out",
                  circleGrob(r=.4, gp=gpar(col=NA, fill=4)))
grid.define(grob, name="donut")
grid.segments(gp=gpar(col=3, lwd=50))
pushViewport(viewport(height=1/3))
grid.use("donut")
popViewport()

以下代码类似,但这一次我们在图像宽度三分之一的视口中使用该组,因此该组被水平压扁。

此示例中的另一个区别是,该组基于使用“even-odd”规则填充的路径,这表明我们可以组合组和路径的这些新特性。

grid.newpage()
grob <- fillGrob(path,
                 rule="evenodd",
                 gp=gpar(col=NA, fill=4))
grid.define(grob, name="donut")
grid.segments(gp=gpar(col=3, lwd=50))
pushViewport(viewport(width=1/3))
grid.use("donut")
popViewport()

以下代码演示我们可以多次使用一个组。在这种情况下,我们在另外两个视口中使用该组,这两个视口仍然是正方形,但比图像小,并且向左或向右移动。

此示例中的另一个区别是,该组基于在应用了亮度蒙版的视口中绘制的矩形;进一步证明了我们能够将各种图形工具结合在一起使用。

pdf("luminance-mask-squashed.pdf", width=2, height=2)
vp <- viewport(mask=as.mask(mask, "luminance"))
grob <- rectGrob(gp=gpar(fill=4), vp=vp)
grid.define(grob, name="donut")
grid.segments(gp=gpar(col=3, lwd=50))
pushViewport(viewport(x=.25, width=.5, height=.5))
grid.use("donut")
popViewport()
pushViewport(viewport(x=.75, width=.5, height=.5))
grid.use("donut")
popViewport()
dev.off()

demonstration of affine transformtion of luminance masked output

到目前为止,新特性仅在图形设备子集中实现:cairo_pdf()cairo_ps()x11(type="cairo")png(type="cairo")jpeg(type="cairo")tiff(type="cairo")svg()quartz()(来自 R 4.3.0)和 pdf()。此外,大多数合成运算符仅适用于 Cairo 设备或 quartz(),Cairo 设备仅支持 alpha 蒙版,quartz() 仅支持亮度蒙版。

实现图形设备的 R 包需要针对新 R 版本进行更新和重新安装。

有关新功能的进一步讨论和更多详细信息以及如何实施这些功能,请参阅一系列技术报告:一篇关于,一篇关于路径,还有一篇关于蒙版