I have a question on how to add transparency to cached background-layers… I tried my best to come up with a feasible solution, but so far I’m a bit lost so I thought I’ll give it a try and ask if there’s a simple way to do what I want:
In short, I’m the developer of EOmaps, a library that uses matplotlib figures to analyze geographical data.
In order to overlay different plot-layers, I use blitting to save and restore previously fetched layers.
The layers are stored as BufferRegions using:
saved_layer = canvas.copy_from_bbox(bbox)
and restored via
canvas.restore_region(saved_layer, bbox=bbox, xy=xy).
Now my question is the following:
Is it possible to add transparency to the region restored via
… to make myself clear, I want to store a non transparent layer and restore it with arbitrary transparency.
… here’s a gif to make it even more clear (I’d like to be able to set the transparency of the overlay)
Any insights on this would be highly appreciated!
Can you convert to a RGBA image and use draw_image instead?
Thinking about it loud, I wonder, in fact, whether we could just (after deprecation) get rid of restore_region and BufferRegions and replace their uses by draw_image (having copy_from_bbox return an RGBA image instead).
Hey, thanks for the response!!
yes, converting the buffer to rgba should not be a problem…
I’d use something like this:
buf = saved_background.to_string_argb()
ncols, nrows = saved_background.get_extents()[2:]
argb = np.frombuffer(buf, dtype=np.uint8).reshape(nrows, ncols, 4)
however, it seems that
canvas does not have a
what exactly do you mean by
Sorry, that method is on the (cached) renderer, i.e.
fig.canvas.get_renderer() (after a draw has been made). See matplotlib.backend_bases — Matplotlib 3.5.3 documentation.
OK thanks! … always scares me a little when I have to look that deep into the matplotlib code-base
I found the function, but I can’t seem to get it working properly and the documentation is starting to get a bit sparse… (especially the
gc argument puzzles me a bit…)
I crafted a minimal working example that shows how I do it currently and where I’d like to incorporate your suggestion… could you maybe help me out by quickly showing how to use
draw_image in such a context?
import numpy as np
import matplotlib.pyplot as plt
f, ax = plt.subplots()
buffer = f.canvas.copy_from_bbox(ax.bbox)
x = buffer.get_extents()
ncols, nrows = x - x, x - x
argb = np.frombuffer(buffer, dtype=np.uint8).reshape((nrows, ncols, 4))
rgba = argb[...,(1,2,3,0)]
# this is how i currently restore partial regions
# (but I can't figure out how to properly add transparency here)
f.canvas.restore_region(buffer, (200, 200, 400, 400), (20, 50))
# ------ how would I implement this with draw_image ???
# (the code below does not seem to work)
# gc = self.renderer.new_gc()
# renderer.draw_image(gc, 0, 0, rgba)
OK… i figured it out myself… the only thing that was missing in the above code was to set a proper clipping region
gc = self.renderer.new_gc()
renderer.draw_image(gc, 0, 0, rgba)
THANKS A LOT for pointing me into the right direction !!!
Soon it will be possible to transparently overlay arbitrary layers in EOmaps
@raphaelquast Trying to understand what your actual application is doing: are you rendering two figures and then blitting from one on to the other?
basically yes… but I do all in one figure…
I use a rather extensive adaption of the BlitManager to create some kind of a “multi-layer interface” for a figure that caches already rendered layers so that I can use blitting to compare them without having to re-draw.
EOmaps is intended for analysis of very large datasets (and/or WebMap layers) so I try to avoid re-drawing as much as possible…
I guess it would be possible to use 2 separate figures for that, but then I would have to carefully connect the two so that they are always properly aligned despite of events such as resize/zoom/pan etc…
I was worried about exactly those issues with multiple figures!
I still do not see how you do the dynamic sub-setting with multiple layers though (or maybe restore_region is more flexible than I remember off the top of my head…).
Not sure if I understand your question correctly…
Let me try to explain in principle what I do:
- draw something on the axis (e.g. the effective overlay)
- render & cache the figure
- clear the axis
- draw something again (e.g. the effective background layer)
- implement the
on_draw callback as shown above to overlay the previously drawn figure on each draw event
draw_image can be used to restore a subset of the cached render at an arbitrary position within the figure… so in the end I’m using a
"motion_notify_event" to get the cursor position and update the position and size of the region drawn with
Finally I implemented callbacks that detect if a re-draw of the cached overlay is necessary (e.g. on resize/pan/zoom events)
Does this make it more clear?
Now, both restore_region and draw_image can be used to restore a subset of the cached render at an arbitrary position within the figure
Yes, that was the detail of the API I was not remembering.
That is all very clever!