Masking a Drawable/Bitmap on Android

21,492

Solution 1

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();

Solution 2

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!

Solution 3

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

Solution 4

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.

Solution 5

Using the Xfermodes example in the API Demo I was able to use a PorterDuffXfermode applied to a Paint object to blend together two bitmaps on a canvas. This works exactly how I need it to.

Share:
21,492
Joshua Rodgers
Author by

Joshua Rodgers

Software Engineer

Updated on July 09, 2022

Comments

  • Joshua Rodgers
    Joshua Rodgers almost 2 years

    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.