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

ggsave() doesn't recognize .tif extension and device = agg_tiff() or tiff() is ignored #5323

Closed
twest820 opened this issue Jun 9, 2023 · 11 comments

Comments

@twest820
Copy link

twest820 commented Jun 9, 2023

Attempted hello world, failed badly (.png and .jpg are fine as usual).

ggsave("foo.tif")  # try to write a TIFF using the most commonly file extension
Error in `ggsave()`:
! Unknown graphics device "tif"

ggsave("foo.tif", device = tiff(compression = "lzw")) # try specifying the graphics device explicitly
Error in `ggsave()`:
! Unknown graphics device "tif"

ggsave("foo.tif", device = ragg::agg_tiff(compression = "lzw"))
Error in `ggsave()`:
! Unknown graphics device "tif"

ggsave("foo.tiff", device = tiff(compression = "lzw")) # produces an uncompressed TIFF file

ggsave("foo.tiff", device = ragg::agg_tiff(compression = "lzw")) # also produces an uncompressed TIFF file and additionally doesn't honor (or at least warn about overriding) agg_tiff()'s other settings

From replies in #3525 it appears tiff(compression = "lzw") might have worked in 2019 and reverted since.

RStudio 2023.06.0 with AGG backend, R 4.2.3, ggplot 3.4.2.

@teunbrand
Copy link
Collaborator

Sorry, maybe I'm misunderstanding, but the request here is that "foo.tif" be recognised as the same device as "foo.tiff"?
I don't think device accepts a function call, so you'd have to pass compression = "lzw" to the ... argument instead of as part of the device call.

@twest820
Copy link
Author

I don't think device accepts a function call

Hi, I think the concept you're looking for is a constructor. That's a function call which returns, in this case, the relevant device object which should be used in writing the file. The use of a device constructor here follows the documentation (and quite a few examples).

the request here is that "foo.tif" be recognised as the same device as "foo.tiff"?

That would address one of the two issues here, yes.

@teunbrand
Copy link
Collaborator

But the device functions aren't constructors and return NULL. I'm afraid I can't find the documentation and examples you mention in neither ggplot2's docs or the ggplot2 book.

I think this is the recommended syntax, which is in working order even if you use .tif instead of .tiff:

ggsave("foo.tif", device = ragg::agg_tiff, compression = "lzw")

@twest820
Copy link
Author

It's in the ggsave() documentation:

device
Device to use. Can either be a device function (e.g. png), or one of "eps", "ps", "tex" (pictex), "pdf", "jpeg", "tiff", "png", "bmp", "svg" or "wmf" (windows only).

tiff() is a device function per the png link provided.

@teunbrand
Copy link
Collaborator

Right but device = tiff is providing a device function to the argument whereas device = tiff() is providing a function call. I don't think the function call is recommended anywhere.

@teunbrand
Copy link
Collaborator

The .tif extension is now a .tiff synonym on the development branch.

@teunbrand
Copy link
Collaborator

I'm going to mark this as completed, as it seems unlikely we're going to change the ggsave-device interface.

@twest820
Copy link
Author

If the interface isn't changing then the documentation is clearly incorrect, in which case it follows from the reasoning above the documentation is not a recommendation for how to use the code. Since it's difficult to argue such a situation constitutes completion I suggest the ggplot team reconsider.

@teunbrand
Copy link
Collaborator

teunbrand commented Jun 30, 2023

I'll explain how I reason about the documentation. For completeness, it reads:

device Device to use. Can either be a device function (e.g. png), or one of "eps", "ps", "tex" (pictex), "pdf", "jpeg", "tiff", "png", "bmp", "svg" or "wmf" (windows only).

So it can either be device = png, because png is a symbol for a function, or it can be device = "png" because that is one of the strings allowed.

device = png(), i.e. passing a function call, is meaningless. All it does is it passes NULL as an argument, which prompts ggsave() to look at the file extension for a device to use. Sure as a side effect it opens a device, but that opened device is explicitly ignored and restored upon exit. This is why the following syntax from your examples doesn't produce a compressed file:

ggsave("foo.tiff", device = tiff(compression = "lzw")) # produces an uncompressed TIFF file

How is the documentation incorrect? It is not at all clear to me.

@twest820
Copy link
Author

As noted above, the documentation and most available examples say to use device = tiff() (or equivalents thereof for other output formats). A look back into github's history shows the same pattern's also frequently present in other ggplot issues filed around this topic, albeit not as much in the past five years or so.

Per comments in this issue, device function design now appears to motivate argument expansion in the form of ggsave(device = tiff, compression = ?, bg = ?, type = ?, antialias = ?, ...), suggesting ggsave() acts as a shorthand for deviceFunction(filename, ...); print(); dev.off() (I get no hits for ggsave()'s implementing code in the ggplot2 repo). Given that, it's curious there's nothing in the ggsave() documentation around the ... "parameter" which illustrates the call pattern or discusses how to form the device function arguments―there's the device = "pdf" example without arguments but that's it. It's also odd the documentation for device that's quoted above omits the default NULL as a valid case. I think an easy clarification there would be to move and revise the sentence about extension guessing.

Past issues in this area show fragility around conflicts between ggsave()'s own arguments and overlapping device function arguments. So it seems additionally strange there's no discussion of argument precedence and how that affects overall output size in pixels and the relative size of text within the output.

@teunbrand
Copy link
Collaborator

As noted above, the documentation and most available examples say to use device = tiff()

I simply cannot find any examples of this, so I'm inclined to dismiss this argument, but willing to be proven wrong.

ggsave() acts as a shorthand for deviceFunction(filename, ...); print(); dev.off()

Yeah that seems broadly correct.

there's nothing in the ggsave() documentation around the ... "parameter" which illustrates the call pattern or discusses how to form the device function arguments

The documentation for ... reads: "Other arguments passed on to the graphics device function, as specified by device.". This contradicts the assertion that there is no documentation. I agree that there are no examples illustrating the use, but ... is broadly understood to pass on extra arguments to downstream functions, not just in ggsave() or ggplot2.

I think an easy clarification there would be to move and revise the sentence about extension guessing.

I'd agree that might be an improvement.

Past issues in this area show fragility around conflicts between ggsave()'s own arguments and overlapping device function arguments.

Again, I'm having a hard time finding examples of this.

So it seems additionally strange there's no discussion of argument precedence and how that affects overall output size in pixels and the relative size of text within the output.

I think mostly this mostly depends on device implementation and the best thing to do would be to lookup the documentation of the device rather than ggsave().

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

No branches or pull requests

2 participants