r-lib/ragg

Precision bug in gradients?

Closed this issue · 8 comments

Hi Thomas,

I think I might have found a precision bug with linear gradients in {ragg}.

I'm aiming to fill an object with a gradient flanked by two flat colours.
Smugly pleased with myself, I imagined I could do this by using tiny increments in the stops argument of grid::linearGradient().
Unfortunately, the two flanking colours do not show up with these small increments.
They do show up when I use stops = c(0, 0.01, 0.99, 1).

library(grid)

grad <- linearGradient(
  colour = c("tomato", "white", "black", "dodgerblue"),
  stops  = c(0, 0.0001, 0.9999, 1),
  y1 = 0.5, y2 = 0.5,
  x1 = unit(0.1, "npc"), 
  x2 = unit(0.9, "npc"),
)
rect <- rectGrob(gp = gpar(fill = grad))

tmp1 <- tempfile(fileext = ".png")
tmp2 <- tempfile(fileext = ".png")

ragg::agg_png(tmp1)
grid.newpage()
grid.draw(rect)
dev.off()
#> png 
#>   2
knitr::include_graphics(tmp1)

Drawing the same rectangle with the regular png device, it looks like I expected.

png(tmp2, type = "cairo-png")
grid.newpage()
grid.draw(rect)
dev.off()
#> png 
#>   2
knitr::include_graphics(tmp2)

Created on 2024-01-06 with reprex v2.0.2

Yeah, AGG uses a precalculated look-up table for gradients, which I have hardcoded to 512 stops, so at some point that breaks down. I could increase it for a slight performance penalty, but then you could decrease your stops and we would arrive at the same issue again...

Not sure if there is a perfect solution here

I know nothing about the innards about AGG, but if the stops are analogous to bins along the gradient, couldn't the first and last stop be centered at the start- and endpoints (instead of aligned with them)? If my understanding is somewhat correct, that wouldn't require a bump in the lookup table resolution, but I do not know how feasible this would be.

I don't have any control over how AGG does this

Alright then let me close this and I'll make sure that there is at least a 1/512th difference between the outer stops in my application, which shouldn't be visible to the naked eye anyway.

I mean, I'm fine bumping it to 1024 or something. My point was mainly that there will always be a limit that comes way before the limitations of floating point precision

Then I have to make sure there is a 1/1024th difference in the outer stops, which is the same solution with a different number. Is there no way to ensure that the padding colour is the exact colour associated with the most extreme stops, rather than the 1st and last colour in the LUT?

No, that is not how it works :-)

Alright I'll accept my fate :p