androidandroid-imageviewandroid-custom-viewandroid-color

How to make color filter round on an Android ImageView?


I'm using a custom class (extension of ImageView) to have an XML round image view. The problem is when I call .setColorFilter() it doesn't adhere to the same circular/round bounds.

How can I make the color filter only affect the image and not the entire rectangle of the view?

Here is my custom class for reference:

public class RoundedCornerImageFilterView extends ImageFilterView {
    public RoundedCornerImageFilterView(Context context) {
        super(context);
    }

    public RoundedCornerImageFilterView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public RoundedCornerImageFilterView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setImageFilter(int color) {
        this.setColorFilter(color, PorterDuff.Mode.SRC_IN);
    }

    @Override
    protected void onDraw(Canvas canvas) {

        Drawable drawable = getDrawable();

        if (drawable == null) {
            return;
        }

        if (getWidth() == 0 || getHeight() == 0) {
            return;
        }
        Bitmap b = ((BitmapDrawable) drawable).getBitmap();
        Bitmap bitmap = b.copy(Bitmap.Config.ARGB_8888, true);
        int w = getWidth();
        int h = getHeight();
        Bitmap roundedCornerBitmap = getRoundedCornerBitmap(bitmap, h, w);
        canvas.drawBitmap(roundedCornerBitmap, 0, 0, null);

    }

    public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, int height, int width) {
        Bitmap sbmp;
        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
                bitmap.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(output);

        final int color = 0xff424242;
        final Paint paint = new Paint();
        final Rect rect = new Rect(0, 0,
                (width), (height));
        final RectF rectF = new RectF(rect);
        final float roundPx = 28;

        paint.setAntiAlias(true);
        canvas.drawARGB(0, 0, 0, 0);
        paint.setColor(color);
        canvas.drawRoundRect(rectF, roundPx, roundPx, paint);

        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(bitmap, rect, rect, paint);

        return output;
    }

}

My xml implementation:

<MY_PATH.RoundedCornerImageFilterView
    android:id="@+id/MY_IMAGE_VIEW"
    android:layout_width="match_parent"
    android:layout_height="150dp"
    />

Me trying to set the color filter:

MY_IMAGE_VIEW.setColorFilter(Color.parseColor(color), PorterDuff.Mode.OVERLAY)

Before the filter (looking like it's supposed to): pre color filter rounded image view

After setting the filter (you can see the square edges now): after color filter applied rounded image view


Solution

  • Although the image (bitmap) has been given rounded corners, the canvas that it is written to has not. Since the color filter is being applied to the canvas, the tint spills out into the corners.

    I suggest that you apply a rounded rectangle to a path then clip the path to the canvas. Something like this:

    public class RoundedImageView extends AppCompatImageView {
        private final Path mPath = new Path();
    
        public RoundedImageView(Context context) {
            super(context);
            init();
        }
    
        public RoundedImageView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public RoundedImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        private void init() {
            setColorFilter(Color.RED, PorterDuff.Mode.OVERLAY);
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            mPath.reset();
            mPath.addRoundRect(new RectF(0, 0, w, h), 128, 128, Path.Direction.CW);
        }
    
        @Override
        public void draw(Canvas canvas) {
            canvas.save();
            canvas.clipPath(mPath);
            super.draw(canvas);
            canvas.restore();
        }
    }
    

    enter image description here

    I am using ImageView here, but the concept remains the same.

    If you do this type of clipping, then rounding the bitmap becomes superfluous since it will also be clipped to the path.