简体   繁体   English

初始化图形设备时如何设置设备复制参数?

[英]How do I set the device-copy parameters when initializing a graphics device?

I'm working on a graphics device in a R package, and need to set some graphical parameters for text / labels on the devices as they are initialized or reset.我正在使用 R package 中的图形设备,并且在初始化或重置设备时需要为设备上的文本/标签设置一些图形参数。

This feature is described in R Internals: R Internals 中描述了此功能:

The three copies of the GPar structure are used to store the current parameters (accessed via gpptr), the 'device copy' (accessed via dpptr) and space for a saved copy of the 'device copy' parameters. GPar 结构的三个副本用于存储当前参数(通过 gpptr 访问)、“设备副本”(通过 dpptr 访问)和保存的“设备副本”参数副本的空间。 The current parameters are, clearly, those currently in use and are copied from the 'device copy' whenever plot.new() is called (whether or not that advances to the next 'page').显然,当前参数是当前正在使用的参数,并且在调用 plot.new() 时从“设备副本”复制(无论是否前进到下一个“页面”)。 The saved copy keeps the state when the device was last completely cleared (eg when plot.new() was called with par(new=TRUE)), and is used to replay the display list.保存的副本保留上次完全清除设备时的 state(例如,使用 par(new=TRUE) 调用 plot.new() 时),并用于重播显示列表。

How do I, from a package, actually access and initialize the "device copy"?我如何从 package 实际访问和初始化“设备副本”?

All I've been able to find is a comment containing an older, copy-pasted comment in GraphicsDevice.h :我所能找到的只是在GraphicsDevice.h中包含一个旧的、复制粘贴的评论的评论:

  * 2. I found this comment in the doc for dev_Open -- looks nasty
  *    Any known instances of such a thing happening?  Should be
  *    replaced by a function to query the device for preferred gpars
  *    settings? (to be called when the device is initialised)
          *
          * NOTE that it is perfectly acceptable for this
          * function to set generic graphics parameters too
          * (i.e., override the generic parameter settings
          * which GInit sets up) all at the author's own risk
          * of course :)

I don't know if I fully understand what you're trying to do, but I think you may find this guide to be useful.我不知道我是否完全理解您想要做什么,但我认为您可能会发现本指南很有用。 Some key excerpts:一些关键摘录:

To create a running graphics device with our own functions, we call the graphicsDevice() function.要使用我们自己的函数创建一个正在运行的图形设备,我们调用 graphicsDevice() function。 While there are several methods for this, essentially we give it a list of named functions that specify the implementation of some or all of the 21 graphical primitive operations.虽然有几种方法可以做到这一点,但基本上我们给它一个命名函数列表,这些函数指定了 21 个图形基元操作中的部分或全部的实现。 We might give this as a list or as an instance of RDevDescMethods or of a sub-class that we define for a particular type of device.我们可以将其作为列表或 RDevDescMethods 的实例或我们为特定类型的设备定义的子类给出。 So we focus on writing these functions.所以我们专注于编写这些函数。

Then:然后:

Each of the methods is passed an object of class DevDescPtr.每个方法都通过 class DevDescPtr 的 object。 This is also the type of the value returned by the top-level function graphicsDevice().这也是顶层 function graphicsDevice() 返回值的类型。 This is a reference the C-level data structure that represents the graphics device.这是代表图形设备的 C 级数据结构的参考。 We can use this to query the settings of the graphics device.我们可以使用它来查询图形设备的设置。

Some of these fields in the device are used when initializing the device rather than within the functions (eg those whose names are prefixed with "start").设备中的某些字段在初始化设备时使用,而不是在函数中使用(例如,名称以“start”为前缀的那些)。 Other fields are structural information about the rendering of different aspects of the device.其他字段是有关设备不同方面呈现的结构信息。 For example, we can find the dimensions of the drawing area, The DevDescPtr class is essentially an opaque data type in R (containing an external pointer to the C-level data structure) and is intended to be used as if it were an R-level list.例如,我们可以找到绘图区域的尺寸,DevDescPtr class 本质上是 R 中的一种不透明数据类型(包含指向 C 级数据结构的外部指针),旨在将其用作 R-级别列表。 We can use the $ operator to access individual fields and we can find the names of these fields with names().我们可以使用 $ 运算符来访问各个字段,我们可以使用 names() 找到这些字段的名称。

and finally:最后:

Under some rare circumstances, it is convenient to convert the reference to an R object.在极少数情况下,将引用转换为 R object 会很方便。 We can do this by coercing it to the corresponding R class named DevDesc (ie with the "Ptr" remove), ie as(dev, "DevDesc").我们可以通过将其强制转换为名为 DevDesc 的相应 R class(即删除“Ptr”),即 as(dev, "DevDesc") 来做到这一点。 This copies each of the fields in the C-level structure to the corresponding slot in the R class.这会将 C 级结构中的每个字段复制到 R class 中的相应插槽。

For example, the circle method of the device has this signature:例如,设备的circle方法有这样的签名:

circle ( numeric, numeric, numeric, R_GE_gcontextPtr, DevDescPtr )

R_GE_gcontextPtr is: R_GE_gcontextPtr 是:

...another reference to an instance of a C-level data type. ...对 C 级数据类型实例的另一个引用。 This is the information about the "current" settings of the device.这是有关设备“当前”设置的信息。 This gives us information about the current pen/foreground color, the background color, the setting for the gamma level, the line width, style, join, the character point size and expansion/magnification, and the font information.这为我们提供了有关当前笔/前景色、背景色、伽马级别设置、线宽、样式、连接、字符点大小和扩展/放大率以及字体信息的信息。 The available fields are可用的字段是

names(new("R_GE_gcontextPtr"))
 [1] "col"        "fill"       "gamma"      "lwd"        "lty"       
 [6] "lend"       "ljoin"      "lmitre"     "cex"        "ps"        
[11] "lineheight" "fontface"   "fontfamily"

Caveat警告

I will present a solution here that predominantly uses C++ code.我将在这里介绍一个主要使用 C++ 代码的解决方案。 To make it more reproducible so that it can be run from within an R console I have done this using Rcpp::cppFunction .为了使其更具重现性,以便它可以在 R 控制台中运行,我使用Rcpp::cppFunction完成了此操作。 However, this is clearly not the method you would use while building a package.但是,这显然不是您在构建 package 时会使用的方法。 The resultant functions work by accessing raw pointers to R graphics devices that the user must specify, and if you call them using a non-existent device number your R session will crash.生成的函数通过访问指向用户必须指定的 R 图形设备的原始指针来工作,如果您使用不存在的设备号调用它们,您的 R Z21D6F40CFB511982E4424E0E25Z5崩溃。


Solution解决方案

The three copies of the GPar structure that these comments are describing are kept together in another structure called baseSystemState , which is defined here .这些注释所描述的GPar结构的三个副本一起保存在另一个称为baseSystemState的结构中,该结构在这里定义。

Each graphics device has a pointer to a baseSystemState , and we can access the graphics device using C or C++ code if we include the header file include/R_ext/GraphicsEngine.h in our own code.每个图形设备都有一个指向baseSystemState的指针,我们可以使用 C 或 C++ 代码访问图形设备,如果我们在自己的include/R_ext/GraphicsEngine.h代码。

However, there's a snag.但是,有一个障碍。 Although we can get a pointer to the baseSystemState struct, our code has no idea what this actually is, since the definition of baseSystemState and GPar are not part of the public API.虽然我们可以得到一个指向baseSystemState结构的指针,但我们的代码并不知道它到底是什么,因为baseSystemStateGPar的定义不是公共 API 的一部分。

So in order to read the baseSystemState and the GPar s it contains, we have to redefine these structures in our own code (as Dirk suggested in his comment).因此,为了读取baseSystemState和它包含的GPar ,我们必须在我们自己的代码中重新定义这些结构(正如 Dirk 在他的评论中所建议的那样)。 Some of the members of GPar are also of types or enums that need to be defined first. GPar的一些成员也是需要首先定义的类型或枚举。

We can take these definitions, compactify them into a single string and use them as includes in a Rcpp::cppFunction call.我们可以采用这些定义,将它们压缩为单个字符串,并将它们用作Rcpp::cppFunction调用中的includes Here is a wrapper function that does that, and therefore allows you to write C++ functions that have access to the existing graphic devices' parameters:这是一个包装器 function 执行此操作,因此允许您编写可以访问现有图形设备参数的 C++ 函数:

cppFunction_graphics <- function(s) 
{
  include <-  paste0("#include \"", R.home("include/R_ext/GraphicsEngine.h\""))
  Rcpp::cppFunction(s, includes = c(include, 
  "typedef enum {DEVICE= 0, NDC= 1, INCHES = 13, 
  NIC = 6, OMA1= 2, OMA2= 3, OMA3 = 4,OMA4= 5,NFC = 7, NPC= 16,USER= 12, MAR1 = 8,
  MAR2= 9, MAR3= 10,MAR4= 11, LINES = 14, CHARS =15 } GUnit; typedef struct {
  double ax; double bx; double ay; double by;} GTrans; typedef struct {int state; 
  Rboolean valid; double adj; Rboolean ann; rcolor bg; char bty; double cex; 
  double lheight; rcolor col; double crt; double din[2]; int  err; rcolor fg; 
  char family[201]; int font; double gamma; int lab[3]; int las; int lty; 
  double lwd; R_GE_lineend lend; R_GE_linejoin ljoin; double lmitre; double mgp[3];
  double mkh; int pch; double ps; int smo; double srt; double tck; double tcl;
  double xaxp[3]; char xaxs; char xaxt; Rboolean xlog; int xpd; int oldxpd;
  double yaxp[3]; char yaxs; char yaxt; Rboolean ylog; double cexbase;
  double cexmain; double cexlab; double cexsub; double cexaxis; int fontmain; 
  int fontlab; int fontsub; int fontaxis; rcolor colmain; rcolor collab; 
  rcolor colsub; rcolor colaxis; Rboolean layout; int numrows; int numcols; 
  int currentFigure; int lastFigure; double heights[200]; double widths[200]; 
  int cmHeights[200]; int cmWidths[200]; unsigned short order[10007]; int rspct;
  unsigned char respect[10007]; int mfind; double fig[4]; double fin[2];
  GUnit fUnits; double plt[4]; double pin[2]; GUnit pUnits; Rboolean defaultFigure;
  Rboolean defaultPlot; double mar[4]; double mai[4]; GUnit mUnits; double mex; 
  double oma[4]; double omi[4]; double omd[4]; GUnit oUnits; char pty; 
  double usr[4]; double logusr[4]; Rboolean new_one; int devmode; 
  double xNDCPerChar; double yNDCPerChar; double xNDCPerLine; double yNDCPerLine; 
  double xNDCPerInch; double yNDCPerInch; GTrans fig2dev; GTrans inner2dev; 
  GTrans ndc2dev; GTrans win2fig; double scale;} GPar; typedef struct {GPar dp; 
  GPar gp; GPar dpSaved; Rboolean baseDevice;} baseSystemState;"),
  env = parent.frame(2))
}

So now we can write a function that will extract or write to the graphics parameters of our choice from the device's starting parameters.所以现在我们可以编写一个 function 从设备的启动参数中提取或写入我们选择的图形参数。 Here, we will get our function to return a list of various colour parameters, but you can return whatever parameters you like from GPar , most of which are self-explanatory in the GPar struct definition在这里,我们将让我们的 function 返回各种颜色参数的列表,但是您可以从GPar返回任何您喜欢的参数,其中大部分在GPar 结构定义中是不言自明的

cppFunction_graphics("

Rcpp::List get_default_GPar(int devnum)
{
  pGEDevDesc dd = GEgetDevice(devnum);
  baseSystemState *bss = (baseSystemState*) dd->gesd[0]->systemSpecific;
  GPar GP = bss->dp;
  auto get_colour = [](rcolor rcol){
    return Rcpp::NumericVector::create(
       Rcpp::Named(\"red\") = rcol & 0xff,
       Rcpp::Named(\"green\") = (rcol >> 8) & 0xff,
       Rcpp::Named(\"blue\") = (rcol >> 16) & 0xff);
    };
  return Rcpp::List::create(Rcpp::Named(\"fg\") = get_colour(GP.fg),
    Rcpp::Named(\"bg\") = get_colour(GP.bg),
    Rcpp::Named(\"col\") = get_colour(GP.col),
    Rcpp::Named(\"colmain\") = get_colour(GP.colmain),
    Rcpp::Named(\"collab\") = get_colour(GP.collab),
    Rcpp::Named(\"colaxis\") = get_colour(GP.colaxis));
}

")

So now in R I can ensure I have a device operating by doing:因此,现在在 R 中,我可以通过以下方式确保设备运行:

plot(1:10)

在此处输入图像描述

And to access the current device's default graphics parameters I can do:要访问当前设备的默认图形参数,我可以这样做:

get_default_GPar(dev.cur() - 1)
#> $fg
#>   red green  blue 
#>     0     0     0 
#> 
#> $bg
#>   red green  blue 
#>   255   255   255 
#> 
#> $col
#>   red green  blue 
#>     0     0     0 
#> 
#> $colmain
#>   red green  blue 
#>     0     0     0 
#> 
#> $collab
#>   red green  blue 
#>     0     0     0 
#> 
#> $colaxis
#>   red green  blue 
#>     0     0     0 

Which gives me the correct values for the default device parameters.这为我提供了默认设备参数的正确值。

Now I can also write to the default device parameters if I define another function.现在,如果我定义另一个 function,我也可以写入默认设备参数。 Suppose I want to be able to change the default colour of the device's labels:假设我希望能够更改设备标签的默认颜色:

cppFunction_graphics("

void set_col(int dn, int red, int green, int blue, int alpha)
{
  int new_col = red | (green << 8) | (blue << 16) | (alpha << 24);
  pGEDevDesc dd = GEgetDevice(dn);
  baseSystemState *bss = (baseSystemState*) dd->gesd[0]->systemSpecific;
  bss->dp.collab = new_col;
}

")

Now I have a function in R that can overwrite the default label colours of the device.现在我在 R 中有一个 function 可以覆盖设备的默认 label 颜色。 Let's make the default labels red:让我们将默认标签设为红色:

set_col(dev.cur() - 1, 255, 0, 0, 255)

So now when I make a new plot on the same device, the labels will automatically be red:所以现在当我在同一台设备上制作新的 plot 时,标签会自动变为红色:

plot(1:10)

在此处输入图像描述

So, as desired, you can change the device's gpars without interfering directly with par .因此,根据需要,您可以更改设备的 gpar 而不直接干扰par

As for accessing the saved GPars and current GPars, this is just a case of changing the line GPar GP = bss->dp;至于访问保存的 GPar 和当前的 GPar,这只是更改行GPar GP = bss->dp;的情况。 to GPar GP = bss->gp or GPar GP = bss->dpSavedGPar GP = bss->gpGPar GP = bss->dpSaved

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM