Question

I have an application that has a requirement that an entire RelativeLayout (which is now a custom view extending it) needs to Masked using a custom image. There are many views in the layout that need to be accessed with touches and are dynamic.

I have successfully masked the region drawing shapes using an answer to another question:

@Override
protected void dispatchDraw(Canvas canvas) {
    Path path = new Path();
    int count = canvas.save();

    path.addCircle(400, 200, 300, Path.Direction.CW);

    canvas.clipPath(path);

    super.dispatchDraw(canvas);
    canvas.restoreToCount(count);

}

However, I need to use an image source as my mask source instead of a shape I can author. I have used the PorterDuff toolkit to alter a Bitmap:

    Bitmap resizedBitmap = Bitmap.createScaledBitmap(mMask, this.getWidth(), this.getHeight(), false);
    Bitmap result = Bitmap.createBitmap(this.getWidth(), this.getHeight(), Bitmap.Config.ARGB_8888); //create another bitmap with same height

    Canvas mCanvas = new Canvas(result);
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    mCanvas.drawBitmap(resizedBitmap, 0, 0, paint);
    paint.setXfermode(null);
    onDrawCanvas.drawBitmap(result, 0,0, paint);

That works for masking an image, but I need to mask with a Clip (I think). Is there a way to derive the Path from this image using a different PorterDuff mode? Or is there a simpler method, much like in iOS where you can just set the layerMask directly with an image? Thanks in advance.

Was it helpful?

Solution

There isn't a direct API in the framework that allows you to trace a solid image mask and generate a Path that you can use to do clipping. If you can access the mask contents as SVG, you might be able to use the path tracing techniques Romain Guy describes here: http://www.curious-creature.org/2013/12/21/android-recipe-4-path-tracing/

While this isn't my favorite technique, the best option I've seen is basically a combination of the items you've already described. We need to create an off-screen buffer (via a Canvas+Bitmap) into which the view contents can be rendered, and then use the transfer mode technique to apply the result onto the Canvas attached to the window. I have not tried this with a ViewGroup in dispatch, but it should work. The following (untested) code would be a starting point:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    if (w != oldw || h != oldh) {
        //Create an area to use for content rendering
        mCanvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mCanvasBitmap);
        mXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
    }
}

@Override
protected void dispatchDraw(Canvas canvas) {
    //Render contents into our buffer
    super.dispatchDraw(mCanvas);

    //Render the mask, clipping with a PorterDuffXfermode
    paint.setXfermode(mXfermode);
    mCanvas.drawBitmap(mMask, 0, 0, paint);

    //Clear state and transfer result
    paint.setXfermode(null);
    canvas.drawBitmap(mCanvasBitmap, 0, 0, paint);
}

One drawback to this approach is memory; with a sufficiently large view you must allocate an extra block equal to the size of the view container. We can minimize this impact by doing it once and reusing the buffer.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top