Masking a Drawable/Bitmap on Android
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 acceleratedsetLayerType(View.LAYER_TYPE_SOFTWARE, null);// Using destination shape as a mask// For a good explanation of PorterDuff transfer modes : http://ssp.impulsetrain.com/porterduff.htmlmaskingPaint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));maskingPaint.setAntiAlias(true);// Position the maskmask.setBounds(<insert your mask bounds here>);
Then finally, the onDraw() method applies the mask:
@Overrideprotected 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