Masking an image to a non-rectangular shape

I’m pretty far along with a node for an upcoming convention where the main content areas are the left topic player which is 1/3 the screen, and the right topic player which is the right 2/3 of the screen:

On the left topic player, it cycles through different visualizations of events and can show full images, and when it does, I get the nice “slash” masking because the right topic player with the blue background is drawn over top.

However, to get a “full-topic-player” image on the right panel, since it’s drawn on top, it would show up as a rectangle. I’m trying to avoid having the announcements-making team that will make the images have to make perfectly shaped PNGs for the right player. Is there a way I could mask the uploaded asset images into this shape? It doesn’t look like the image drawing functions have a way to mask. I’ve been looking at maybe using a shader to multiply a mask image with the resource, but I’m very new to shaders and am not sure if info-beamer always applies shaders to the entire visible screen, or it can apply to a specific resource.

Another option might be to use PIL from a Python service, but I’m nervous the order of operations between an asset being added, then PIL masking it might mean a frame or two of the unmasked image spilling out of its topic player?

What’s the best way to go about this?

That should be possible. Create a mask PNG file with one of the R, G, B or A channels being used as mask. I guess alpha would be the obvious choice and used in the code below. Load that image as usual. Then create a shader like this:

local shader = resource.create_shader[[
      uniform sampler2D mask;
      uniform sampler2D Texture;
      varying vec2 TexCoord;

      void main() {
           vec4 img = texture2D(Texture, TexCoord).rgba;
           img.a = texture2D(mask, TexCoord).a;
           gl_FragColor = img;
      }
]]

This should get the RGBA from your image, but then overwrites the alpha value by the one within the provided mask texture. Use it like this:

-- When rendering 'the_image', do this:
shader:use{
    mask = the_mask_image;
}
the_image:draw(...)
shader:deactivate()

Can’t test right now, but that should be it.

This is pretty cool. Is there a way to make a shader mask across multiple draw calls? Like if I wanted to draw a background image, some scrolling text, an animated image, and have the shader clip things in something like screen space (not sure if that’s the right term)?

Edit: The shader slows down my Raspberry Pi 3 a little. Are there any tips that might speed it up? I know it’s a lot of pixels for such a limited device. I’m going to see if a Pi 4 has any issues.

I’m pretty excited with how it looks: Imgur: The magic of the Internet.

1 Like

Not sure to be honest. There’s scissors (which info-beamer unofficially exposes), but they are always rectangular, so that wouldn’t work in your case.

An alternative might be to render the content you intend to clip into a child (using render_child) and then use the shader once when rendering the resulting image object. But that’s also not going to be very fast either and might be awkward to use. And doesn’t work in combination with raw videos are they are rendered outside of the GL context, so cannot be captured by render_child.

Depending on what you intend to mask, you might also draw all the background, then use a partially transparent PNG on top of that, then draw the foreground content on the non-masked area of the PNG.

Drawing the forward content last is actually how I handled the mask on the left-side content in the sign that I’m working on, but one side will always need a more active approach. Maybe I can use render_child so I only have to run the shader once in a while, instead of every frame. It seems to be performant enough on the Pi 4, so I may have to look into it more next year.

1 Like