Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Masking a Drawable/Bitmap on Android

I'm currently looking for a way to use a black and white bitmap to mask the alpha channel of another bitmap or Drawable on Android. I'm curious as to what the best way to do this is. I certainly have a couple of ideas for how to do this, but they are not optimal.

I need to be able to apply a new mask to the image every so often (the black and white bitmap will change every few seconds).

Any feedback on how to achieve this would be greatly appreciated.

like image 587
Joshua Rodgers Avatar asked Apr 12 '10 17:04

Joshua Rodgers


People also ask

What is bitmap masking?

A Bitmap Mask combines the alpha (opacity) of a masked pixel with the alpha of another pixel. Unlike the Geometry Mask, which is a clipping path, a Bitmap Mask behaves like an alpha mask, not a clipping path. It is only available when using the WebGL Renderer.

What is bitmap drawable in Android?

A bitmap is simply a rectangle of pixels. Each pixel can be set to a given color but exactly what color depends on the type of the pixel. The first two parameters give the width and the height in pixels. The third parameter specifies the type of pixel you want to use.


4 Answers

My solution is close to @over_optimistic's solution, less one saveLayer() call. I use a Drawable mask instead of a path, in my case it was a disc.

I declared these variables as fields (it's good practice to allocate memory outside of onDraw method):

private Paint maskingPaint = new Paint();
private Drawable mask = <insert your drawable here>

Then, somewhere outside of onDraw(), setup the objects:

// Xfermode won't work if hardware accelerated
setLayerType(View.LAYER_TYPE_SOFTWARE, null);

// Using destination shape as a mask
// For a good explanation of PorterDuff transfer modes : http://ssp.impulsetrain.com/porterduff.html
maskingPaint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
maskingPaint.setAntiAlias(true);

// Position the mask
mask.setBounds(<insert your mask bounds here>);

Then finally, the onDraw() method applies the mask:

@Override
protected synchronized void onDraw(Canvas canvas)
{
    // Draw the mask first, making it the PorterDuff destination
    mask.draw(canvas);

    // Save the layer with the masking paint, that will be applied on restore()
    // Using CLIP_TO_LAYER_SAVE_FLAG to avoid any overflow of the masked image outside the mask bounds.
    Rect bounds = mask.getBounds();
    canvas.saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, maskingPaint, 
            Canvas.CLIP_TO_LAYER_SAVE_FLAG);

    // Draw the shape offscreen, making it the PorterDuff source
    super.onDraw(canvas);

    // Apply the source to the destination, using SRC_IN transfer mode
    canvas.restore();
}

For a better understanding of the transfer modes, I referred to http://ssp.impulsetrain.com/porterduff.html. That page is pretty interesting to read. After that, with the same kind of code you'll be able to acheive much more than a simple mask!

like image 81
JM Lord Avatar answered Nov 01 '22 02:11

JM Lord


I got it working, so it's something like this

    // we first same the layer, rectF is the area of interest we plan on drawing
    // this will create an offscreen bitmap
    canvas.saveLayer(rectF, null, Canvas.MATRIX_SAVE_FLAG
            | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
            | Canvas.FULL_COLOR_LAYER_SAVE_FLAG
            | Canvas.CLIP_TO_LAYER_SAVE_FLAG);

    // draw our unmasked stuff
    super.draw(canvas);
    // We same a layer again but this time we pass a paint object to mask
    // the above layer
    maskPaint = new Paint()
    maskPaint.setColor(0xFFFFFFFF);
    maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));

    canvas.saveLayer(rectF, maskPaint,
            Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG
                    | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
                    | Canvas.FULL_COLOR_LAYER_SAVE_FLAG
                    | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
    // we draw the mask which is black and white. In my case
    // I have a path, and I use a blurPaint which blurs the mask slightly
    // You can do anti aliasing or whatever you which. Just black & white
    canvas.drawPath(path, blurPaint);
    // We restore twice, this merges the results upward
    // as each saveLayer() allocates a new drawing bitmap
    canvas.restore();
    canvas.restore();
like image 44
over_optimistic Avatar answered Nov 01 '22 03:11

over_optimistic


I made a maskable layout. It's a framelayout where you can specifiy the xporterduffmode and the mask. You can find it here: https://github.com/christophesmet/android_maskable_layout

like image 26
Christophe Smet Avatar answered Nov 01 '22 03:11

Christophe Smet


I'm not entirely clear on what you're going for, but I believe that a combination of BitmapDrawable and LayerDrawable may work. BitmapDrawable will allow you to use your Bitmaps as Drawables, and then you can use LayerDrawable to layer the mask on top of another Drawable.

like image 2
Dan Lew Avatar answered Nov 01 '22 03:11

Dan Lew