Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setting image height/width under new capture output #390

Closed
coatless opened this issue Mar 25, 2024 · 5 comments · Fixed by #399
Closed

Setting image height/width under new capture output #390

coatless opened this issue Mar 25, 2024 · 5 comments · Fixed by #399
Labels
feature New feature or request
Milestone

Comments

@coatless
Copy link
Contributor

With webR v0.3.0+, the graphics for shelter.captureR() were moved into a new .images slot.

However, it doesn't seem like there is a way to constrain image height and/or image width with a pre-call to webr::canvas() since the shelter makes its own call.

parseEvalBare(`{
old_dev <- dev.cur()
webr::canvas(capture = TRUE)
new_dev <- dev.cur()
old_cache <- webr::canvas_cache()
}`, devEnvObj);

So, pre-calling the webr::canvas(width=, height=) before shelter.captureR() seems to have no effect. However, if I move the canvas call into the shelter evaluation, then the graphs are no longer stored inside of .images, but can be retrieved through reading messages after flushing, e.g. the old way from webR v0.2.x.

For a quick example, please consider the Capturing plots documentation entry with only a slight modification to webr::canvas(height=400, width=320):

<html>
  <head>
    <title>WebR Test Graph with Dimensions</title>
  </head>
  <body>
    <div id="plot-output"></div>
    <div>
      <pre><code id="out">Loading webR, please wait...</code></pre>
    </div>

    <script type="module">
      import { WebR } from 'https://webr.r-wasm.org/latest/webr.mjs';
      const webR = new WebR();
      await webR.init();

      const shelter = await new webR.Shelter();

      // <---
      // Attempt to constrain graphics output
      webR.evalRVoid('webr::canvas(width=320, height=400)');
      // ---->

      const capture = await shelter.captureR("demo(graphics)");
      capture.images.forEach((img) => {
        const canvas = document.createElement("canvas");
        // set canvas size
        canvas.width = img.width;
        canvas.height = img.height;
        
        // Draw image
        const ctx = canvas.getContext("2d");
        ctx.drawImage(img, 0, 0, img.width, img.height);
        document.getElementById("plot-output").appendChild(canvas);
      });

      shelter.purge();
    </script>
  </body>
</html>

Note this yields a series of canvas objects with a width and height of 1008 x 1008 instead of the desired 320 x 400 per image

Screenshot of the modified example test to set width and height of image generated

Thus, I think the .images slot contains img objects that do not respect the user supplied width and height values.

@georgestagg
Copy link
Member

georgestagg commented Mar 25, 2024

Hi James,

Try this:

<html>

<head>
  <title>WebR Test Graph with Dimensions</title>
</head>

<body>
  <div id="plot-output"></div>
  <div>
    <pre><code id="out">Loading webR, please wait...</code></pre>
  </div>

  <script type="module">
    import { WebR } from 'https://webr.r-wasm.org/latest/webr.mjs';
    const webR = new WebR();
    await webR.init();

    const shelter = await new webR.Shelter();
    
    // Capture graphics with custom width/height
    const capture = await shelter.captureR(`
        webr::canvas(width = 320, height = 400, capture = TRUE)
        .webr_cvs_id <- dev.cur()
        demo(graphics)
        invisible(dev.off(.webr_cvs_id))
    `);
    capture.images.forEach((img) => {
      const canvas = document.createElement("canvas");
      // set canvas size
      canvas.width = img.width;
      canvas.height = img.height;

      // Draw image
      const ctx = canvas.getContext("2d");
      ctx.drawImage(img, 0, 0, img.width, img.height);
      document.getElementById("plot-output").appendChild(canvas);
    });
    shelter.purge();
  </script>
</body>

</html>

If sufficient, I am happy for this to be added either in src/examples/plot-capture or explicitly in the docs.


I am also currently reconsidering the default width and height for the canvas device, perhaps setting it to look at a session-wide option like getOption("webr.fig.width", default = 504). That would avoid the dance of creating an extra device on the stack inside captureR().

What are your thoughts on such a scheme?

@coatless
Copy link
Contributor Author

Excellent! Just needed to add the capture = TRUE parameter.
Thinking out loud, I don't think this should need a closing statement of dev.off() right?

Regarding a session-wide option, I'm in favor of adding one; however, I do not believe that will be a panacea. There is a need to be able to set/customize image width and height on each call since there are already a few folks tweaking cell-level figure output in surprising ways... 😱

@georgestagg
Copy link
Member

I don't think this should need a closing statement of dev.off() right?

It will work without dev.off(), but including it does no harm and stops the graphics stack from growing over time if there is a long-running session..

I believe there is a limit to the number of devices on the stack, so I would actually recommend adding it. I'll edit the example to add dev.off() now.


There is a need to be able to set/customize image width and height on each call

OK. I will keep thinking. Another option would be something like:

    const capture = await shelter.captureR("demo(graphics)",
     {
        captureGraphicsOptions: { width: 100, height: 200 },
     });

This requires thought to work well, but I needed to get 0.3.x out for other reasons. Hopefully, just opening another device will work for now. I do think we can improve this for the 0.4.x series, though.

@georgestagg georgestagg added the feature New feature or request label Mar 25, 2024
@georgestagg georgestagg added this to the v0.4.x milestone Mar 25, 2024
@coatless
Copy link
Contributor Author

coatless commented Mar 25, 2024

@georgestagg I would enclose the dev.off() in invisible(), e.g. invisible(dev.off()), to suppress the print statement associated with the driver.

No worries regarding the image settings. Something to have on the roadmap for 0.4.z.

One trick that could also be used in the interim with the session option is potentially resetting before each call, e.g.

webR.evalRVoid(`
options("webr.fig.width" = 320)
options("webr.fig.height" = 400)
`)

const capture = await shelter.captureR("demo(graphics)");

However, this would only work if shelter.captureR() checked option contents on each call.

@georgestagg
Copy link
Member

I would enclose the dev.off() in invisible()

Thanks, one should probably also make an attempt to close the right device, e.g.:

webr::canvas(width = 320, height = 400, capture = TRUE)
.webr_cvs_id <- dev.cur()
demo(graphics)
invisible(dev.off(.webr_cvs_id))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants