LibGDX: MaskGroup 🎭

A group that knows how to disguise their children

Vladislav Shesternin
3 min readMar 24, 2022
``Photo by John Noonan on Unsplash

To understand how the masking blending function works, you can read this article: https://veldan1202.medium.com/libgdx-masking-using-blending-function-81c46fdfac33

Android:

MaskGroup:

class MaskGroup(
val mask: Texture? = null,
) : Group(), Disposable {

companion object {
private const val PIXMAP_W = 2000
private const val PIXMAP_H = 2000
}

private val maskTexture: Texture by lazy {
val pixmap = Pixmap(PIXMAP_W, PIXMAP_H, Pixmap.Format.RGBA8888)
pixmap.setColor(1f, 0f, 0f, 0f)
pixmap.fill()

val texture = if (mask == null) {
pixmap.setColor(0f, 0f, 0f, 1f)
pixmap.fillRectangle(
(PIXMAP_W / 2) - (width.toInt() / 2),
(PIXMAP_H / 2) - (height.toInt() / 2),
width.toInt(), height.toInt()
)
Texture(pixmap)
} else Texture(pixmap).combineByCenter(mask)

if (pixmap.isDisposed.not()) pixmap.dispose()
texture
}

override fun dispose() {
maskTexture.dispose()
}

override fun draw(batch: Batch?, parentAlpha: Float) {
if (stage != null) {
batch?.flush()
batch?.drawMask(parentAlpha)
}
}

private fun drawSuper(batch: Batch?, parentAlpha: Float) {
super.draw(batch, parentAlpha)
}

private fun Batch.drawMask(parentAlpha: Float) {
Gdx.gl.glColorMask(false, false, false, true)

setBlendFunction(GL20.GL_ONE, GL20.GL_ZERO)

setColor(0f, 0f, 0f, parentAlpha)
draw(maskTexture,
x + (width / 2) - (maskTexture.width / 2),
y + (height /2) - (maskTexture.height / 2),
maskTexture.width.toFloat(), maskTexture.height.toFloat()
)

drawMasked(parentAlpha)
}

private fun Batch.drawMasked(parentAlpha: Float) {
setBlendFunction(GL20.GL_ZERO, GL20.GL_SRC_ALPHA)
drawSuper(this, parentAlpha)
flush()

Gdx.gl.glColorMask(true, true, true, true)
setBlendFunction(GL20.GL_DST_ALPHA, GL20.GL_ONE_MINUS_DST_ALPHA)
drawSuper(this, parentAlpha)
flush()

setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA)
}

}

fun Texture.combineByCenter(texture: Texture): Texture {
if (this.textureData.isPrepared.not()) this.textureData.prepare()
val pixmap1 = this.textureData.consumePixmap()

if (texture.textureData.isPrepared.not()) texture.textureData.prepare()
val pixmap2 = texture.textureData.consumePixmap()

pixmap1.drawPixmap(pixmap2,
(this.width / 2) - (texture.width / 2),
(this.height / 2) - (texture.height / 2)
)
val textureResult = Texture(pixmap1)

if (pixmap1.isDisposed.not()) pixmap1.dispose()
if (pixmap2.isDisposed.not()) pixmap2.dispose()

return textureResult
}

How to use:

Like a regular group 😉

val mask = MaskGroup()
stage.addActor(mask)
mask.setBounds(0f, 0f, 100f, 100f)

If you create a mask in this way, then the actors added to it will simply be cut off at its edges, this will work like a cutoff, but for some reason it has an advantage over scissors, when your mask with actors rotates they will not be overwritten as it would be with scissors.

Use your mask texture 😎:

val mask = MaskGroup(YourTexture)
stage.addActor(mask)
mask.setBounds(0f, 0f, 100f, 100f)
Example: YourTexture

For example, if you have such a texture for the mask, then the children in the mask will be displayed only where the black area is and not displayed where it is transparent, you can adjust the transparency of the mask, then the children will be displayed with such transparency.

PS. Vel_daN: Love what You DO 💚.

--

--