Method to add transparency to a saved BufferRegion?

Hey,
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 canvas.restore_region(...) ?
… 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 :smile: (I’d like to be able to set the transparency of the overlay)

transparent_blit

Any insights on this would be highly appreciated!
Cheers, Raphael

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 draw_image function…
what exactly do you mean by draw_image ?

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 :sweat_smile:

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()
ax.plot([1,2,3], "ro-")
ax.plot([3,2,1], "go-")
f.canvas.draw()

buffer = f.canvas.copy_from_bbox(ax.bbox)

x = buffer.get_extents()
ncols, nrows = x[2] - x[0], x[3] - x[1]

argb = np.frombuffer(buffer, dtype=np.uint8).reshape((nrows, ncols, 4))
rgba = argb[...,(1,2,3,0)]

def on_draw(self):
    # 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)
    # gc.restore()
    
f.canvas.mpl_connect("draw_event", on_draw)

OK… i figured it out myself… the only thing that was missing in the above code was to set a proper clipping region :smile:

gc = self.renderer.new_gc()
gc.set_clip_rectangle(self.canvas.figure.bbox)
renderer.draw_image(gc, 0, 0, rgba)
gc.restore()

THANKS A LOT for pointing me into the right direction !!!

Soon it will be possible to transparently overlay arbitrary layers in EOmaps :partying_face:

transparent_blit_ready

1 Like

This looks super nice.
See also [MNT]: Replace BufferRegion/restore_region by (x, y, array) tuples and draw_image? · Issue #23882 · matplotlib/matplotlib · GitHub which this discussion has triggered.

@raphaelquast Trying to understand what your actual application is doing: are you rendering two figures and then blitting from one on to the other?

@tacaswell

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…).

@tacaswell
Not sure if I understand your question correctly…
Let me try to explain in principle what I do:

  1. draw something on the axis (e.g. the effective overlay)
  2. render & cache the figure
  3. clear the axis
  4. draw something again (e.g. the effective background layer)
  5. implement the on_draw callback as shown above to overlay the previously drawn figure on each draw event

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… 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 restore_region (or draw_image) accordingly.

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!