4
votes

I am trying to create a method which returns a texture modified by an overlay using libgdx and PixMap.

Assuming I have 2 images: A Base Image in FileHandle textureInput enter image description here

And an overlay image in FileHandle overLay

enter image description here

It should produce this texture:

enter image description here

So it should use the RGB values from the textureInput and the alpha values from the overLay and create the final image. I believe I can do this using the Pixmap class but I just can't seem to find exactly how.

Here is what I gather should be the structure of the method:

public Texture getOverlayTexture(FileHandle overLay, FileHandle textureInput){
    Pixmap inputPix = new Pixmap(textureInput);
    Pixmap overlayPix = new Pixmap(overLay);

    Pixmap outputPix = new Pixmap(inputPix.getWidth(), inputPix.getHeight(), Format.RGBA8888);

    // go over the inputPix and add each byte to the outputPix
    // but only where the same byte is not alpha in the overlayPix

    Texture outputTexture =  new Texture(outputPix, Format.RGBA8888, false);

    inputPix.dispose();
    outputPix.dispose();
    overlayPix.dispose();
    return outputTexture;
}

I am just looking for a bit of direction as to where to go from here. Any help is really appreciated. I apologize if this question is too vague or if my approach is entirely off.

Thanks!

1
Can you not achieve this using the alpha channel?joey.enfield

1 Answers

4
votes

I finally found the way to do this.

How my game is setup is that each item draws itself. They are handed a spritebatch and can do stuff with it. I did it that way various reasons. There is an item manager containing a list of items. Each item has various attributes. Each item has it's own render method along with other independent methods. Here is what finally worked:

A normal item's render method which does not use any alpha masking:

public void render(SpriteBatch batch, int renderLayer) {
    if(renderLayer == Integer.parseInt(render_layer)){              // be in the correct render layer
        batch.draw(item.region, 
                item.position.x,                                    // position.x 
                item.position.y,                                    // position.y
                0,                                                  //origin x 
                0,                                                  //origin y
                item.region.getRegionWidth() ,                      //w 
                item.region.getRegionHeight(),                      //h
                item.t_scale,                                       //scale x
                item.t_scale,                                       //scale y
                item.manager.radiansToDegrees(item.rotation));      //angle
    }
}

So it is handed a spritebatch that it draws to with the correct image, location, scale, and rotation, and that is that.

After playing around with what I found here: https://gist.github.com/mattdesl/6076846 for a while, this finally worked for an item who needs to use alpha masking:

public void render(SpriteBatch batch, int renderLayer) {
    if(renderLayer == Integer.parseInt(render_layer)){
        batch.enableBlending();

        //draw the alpha mask
        drawAlphaMask(batch, item.position.x, item.position.y, item.region.getRegionWidth(), item.region.getRegionHeight());

        //draw our foreground elements
        drawForeground(batch, item.position.x, item.position.y, item.region.getRegionWidth(), item.region.getRegionHeight());
        batch.disableBlending();
    }
}

There is a TextureRegion named alphaMask which contains a black shape. It can be any image, but let's say in this instance its this shape / image:

A "beam"

Here is the function called above that uses that image:

private void drawAlphaMask(SpriteBatch batch, float x, float y, float width, float height) {
    //disable RGB color, only enable ALPHA to the frame buffer
    Gdx.gl.glColorMask(false, false, false, true);

    // Get these values so I can be sure I set them back to how it was
    dst = batch.getBlendDstFunc();
    src = batch.getBlendSrcFunc();

    //change the blending function for our alpha map
    batch.setBlendFunction(GL10.GL_SRC_ALPHA, GL10.GL_ZERO);

    //draw alpha mask sprite
    batch.draw(alphaRegion, 
            x,                                                  // position.x 
            y,                                                  // position.y
            0,                                                  // origin x 
            0,                                                  // origin y
            alphaRegion.getRegionWidth(),                       // w 
            alphaRegion.getRegionHeight(),                      // h
            item.t_scale,                                       // scale x
            item.t_scale,                                       // scale y
            item.manager.radiansToDegrees(item.rotation));      // angle
    //flush the batch to the GPU
    batch.flush();
}

There are a variety of "materials" to apply to any shape. In any instance one of them is assigned to the spriteRegion variable. Let's say right now it is this:

enter image description here

So the drawForeground method called above uses that image like this:

private void drawForeground(SpriteBatch batch, float clipX, float clipY, float clipWidth, float clipHeight) {
    //now that the buffer has our alpha, we simply draw the sprite with the mask applied
    Gdx.gl.glColorMask(true, true, true, true);
    batch.setBlendFunction(GL10.GL_DST_ALPHA, GL10.GL_ONE_MINUS_DST_ALPHA);

    batch.draw(spriteRegion, 
            clipX,                                      // corrected center position.x 
            clipY,                                      // corrected center position.y
            0,                                                  //origin x 
            0,                                                  //origin y
            spriteRegion.getRegionWidth() ,                         //w 
            spriteRegion.getRegionHeight(),                             //h
            item.t_scale,                                       //scale x
            item.t_scale,                                       //scale y
            item.manager.radiansToDegrees(item.rotation));      //angle


    //remember to flush before changing GL states again
    batch.flush();

    // set it back to however it was before
    batch.setBlendFunction(src, dst);
}

That all worked right away in the desktop build, and can produce "Brick Beams" (or whatever) in the game nicely:

enter image description here

However in Android and GWT builds (because after all, I am using libgdx) it did not incorporate the alpha mask, and instead rendered the full brick square.

After a lot of looking around I found this: https://github.com/libgdx/libgdx/wiki/Integrating-libgdx-and-the-device-camera

And so to fix this in Android I modified the MainActivity.java onCreate method like this:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
    cfg.useGL20 = false;
    cfg.r = 8;
    cfg.g = 8;
    cfg.b = 8;
    cfg.a = 8;
    initialize(new SuperContraption("android"), cfg);

    if (graphics.getView() instanceof SurfaceView) {
        SurfaceView glView = (SurfaceView) graphics.getView();
        // force alpha channel - I'm not sure we need this as the GL surface
        // is already using alpha channel
        glView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
    }
}

And that fixes it for Android.

I still cannot figure out how to make it work properly in gwt, as I cannot figure out how to tell libgdx to tell GWT to tell webGl to go ahead and pay attention to the alpha channel. I'm interested in how to do something like this in an easier or less expensive way (though this seems to work fine).

If anyone knows how to make this work with GWT, please post as another answer.

Here is the non-working GWT build if you want to see the texture issue:

https://supercontraption.com/assets/play/index.html