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.
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.
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.
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!
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();
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With