Search code examples
androiddrawableporter-duff

Android: Circular Drawable


I made this Custom Drawable that should clip any Drawable in circle. But with my implementation, the drawable passed is being the output in the original form not in the circular form.

public class CircularDrawable extends Drawable {
    Paint mPaint,xfermodePaint;
    Drawable mDrawable;
    int[] vinylCenter = new int[2];
    int radius;
    Bitmap src;
    PorterDuffXfermode xfermode;
    Rect rect;
    Canvas testCanvas;
    public CircularDrawable(Drawable drawable) {
        mDrawable = drawable;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(0xffffffff);
        mPaint.setStyle(Paint.Style.FILL);
        xfermodePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        xfermode=new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
        xfermodePaint.setXfermode(xfermode);
        testCanvas=new Canvas();
    }

    @Override
    public void setBounds(Rect bounds) {
        super.setBounds(bounds);
        mDrawable.setBounds(bounds);
    }

    @Override
    public void setBounds(int left, int top, int right, int bottom) {
        super.setBounds(left, top, right, bottom);
        mDrawable.setBounds(left, top, right, bottom);
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        vinylCenter[0] = bounds.width() / 2;
        vinylCenter[1] = bounds.height() / 2;
        radius = (bounds.right - bounds.left) / 2;
        src = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888);
        testCanvas.setBitmap(src);
    }


    @Override
    public void draw(Canvas canvas) {
        canvas.save();
        canvas.drawARGB(0, 0, 0, 0);
        canvas.drawCircle(vinylCenter[0],vinylCenter[1],radius,mPaint);
        mDrawable.draw(testCanvas);
        canvas.drawBitmap(src,0f,0f,xfermodePaint);
    }



    @Override
    public void setAlpha(int alpha) {/*ignored*/}

    @Override
    public void setColorFilter(ColorFilter colorFilter) {/*ignored*/}

    @Override
    public int getOpacity() {
        /*ignored*/
        return 0;
    }
}

What is my mistake?

Also I am using a SquareImageview to display this drawable that just makes the view square in onMeasure. This question is not meant for Circular imageview.


Solution

  • The canvas doesnt have alpha channel on by default when using Porter/Duff xfermode mode (As per my experience with my this in android). That is the reason the whole image(src) is being the output. Porter/Duff xfermode acts based on Alpha Channel.

    In many implementation of Circular Drawable 2 Canvas are taken, out of which one is given RGB image bitmap and other is given only alpha channel bitmap with circular mask. Porter/Duff xfermodeis applied and the result is drawn on the main canvas.

    My mistake is that I am not specifying the draw function's canvas that alpha channel is to be considered on using the layer on the canvas for xfermode.

    The draw function will become

    @Override
    public void draw(Canvas canvas) {
        int sc = canvas.saveLayer(null, null,
                        Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
                        );
        canvas.drawCircle(vinylCenter[0],vinylCenter[1],radius,mPaint);
        mDrawable.draw(testCanvas);
        canvas.drawBitmap(src,0f,0f,xfermodePaint);
        canvas.restoreToCount(sc);
    }
    

    Saving the Layer to indicate alpha channel with the flag and then using the xfermode. Finally restoring it to its save count.