41

I would like to change an image I loaded to have round corners.

Any hints, tutorials, best practices you know of?

Janusz
  • 187,060
  • 113
  • 301
  • 369
iamkoa
  • 2,217
  • 6
  • 21
  • 21

8 Answers8

48

For a more controlled method draw a rounded rectangle and mask it onto your image using the porter-duff Xfer mode of the paint.

First setup the Xfer paint and the rounded bitmap:

Bitmap myCoolBitmap = ... ; // <-- Your bitmap you want rounded    
int w = myCoolBitmap.getWidth(), h = myCoolBitmap.getHeight();

// We have to make sure our rounded corners have an alpha channel in most cases
Bitmap rounder = Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(rounder);    

// We're going to apply this paint eventually using a porter-duff xfer mode.
// This will allow us to only overwrite certain pixels. RED is arbitrary. This
// could be any color that was fully opaque (alpha = 255)
Paint xferPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
xferPaint.setColor(Color.RED);

// We're just reusing xferPaint to paint a normal looking rounded box, the 20.f
// is the amount we're rounding by.
canvas.drawRoundRect(new RectF(0,0,w,h), 20.0f, 20.0f, xferPaint);     

// Now we apply the 'magic sauce' to the paint  
xferPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));

Now apply this bitmap ontop of your image:

Bitmap result = Bitmap.createBitmap(myCoolBitmap.getWidth(), myCoolBitmap.getHeight() ,Bitmap.Config.ARGB_8888);
Canvas resultCanvas = new Canvas(result)
resultCanvas.drawBitmap(myCoolBitmap, 0, 0, null);
resultCanvas.drawBitmap(rounder, 0, 0, xferPaint);

Bitmap with rounded corners now resides in result.

atok
  • 5,880
  • 3
  • 33
  • 62
Ralphleon
  • 3,968
  • 5
  • 32
  • 34
  • Wow, very nice! My only concern with this method is speed - if I was applying this technique to nine images at a time, would the app chug? – iamkoa Nov 12 '09 at 19:46
  • 1
    I use this technique in a high performance graphics situation -- and haven't seen any issue with it (even after profiling)! Just make sure to save the xferPaint and the rounder bitmap as a field in your class, creating a bitmap is slow, but drawing it is reasonably fast. – Ralphleon Nov 13 '09 at 19:19
  • It would be great if you could edit some explanations about the parameters you are using into your post. The sample code looks nice but understanding it is not that easy if you haven't worked with this classes already. – Janusz Jun 23 '10 at 09:38
  • How does one use the bitmap after done? ie: how would I set my ImageView's bitmap? Would I set it to myCoolBitmap or to rounder? If I set it to rounder, I see a red rounded rectangle behind my object, if I set it to myCoolBitmap, I see the original (non-rounded) bitmap. – Naftuli Kay Feb 15 '11 at 19:34
  • @Ralphleon, I'm trying utilise this, but running into following issue: I can see black corners on bitmap that is cropped. is there a workaround to make corners really transparent rather then just blacking out? or am I doing something wrong? Thanks! – Sergej Popov Nov 24 '11 at 13:00
  • @SergejPopov make sure you're drawing into an ARGB buffer. Your buffer is most likely missing the alpha component. – Ralphleon Nov 25 '11 at 00:11
  • Shouldn't it be SRC_IN instead of DST_IN? I couldn't get this code to work until I made that change. Also see this: http://www.ibm.com/developerworks/java/library/j-mer0918/ – greg7gkb Apr 24 '12 at 07:01
  • This is great, but how do we round only partial corners? I just need to round the top corners of mine. – Jesse Jul 20 '12 at 16:25
  • 2
    The problem with this approach is that you have to go through an intermediate Bitmap. This can be a problem if you need to draw many different rounded bitmaps quickly and/or often. A simpler approach is to simply call drawRect() with a BitmapShader set on the Paint. It's also easier to write :) – Romain Guy Dec 19 '12 at 09:48
  • @Ralphleon how can I use this if I need a rounded shape with a colored border? I tried to draw a circle with Style.STROKE but it did not work! – moallemi Feb 09 '13 at 05:46
31

Why not use clipPath?

protected void onDraw(Canvas canvas) {
    Path clipPath = new Path();
    float radius = 10.0f;
    float padding = radius / 2;
    int w = this.getWidth();
    int h = this.getHeight();
    clipPath.addRoundRect(new RectF(padding, padding, w - padding, h - padding), radius, radius, Path.Direction.CW);
    canvas.clipPath(clipPath);
    super.onDraw(canvas);
}
Alena
  • 1,080
  • 12
  • 20
Jerry
  • 4,382
  • 2
  • 35
  • 29
  • Haven't tested this yet, but it sounds like this'll do it. Thanks Jerry! – iamkoa Mar 17 '11 at 19:29
  • 3
    Note, after trying this I have found: 1 - you clip canvas first then draw bitmap 2 - it is not faster than using a bitmap mask and the xfer mode overlay 3 - one cannot anti-alias clipPath, while one can anti-alias the paint which draws a mask for the xfer mode overlay. – Lumis Jul 26 '11 at 00:27
  • This worked great for the case I was looking for, although it was not drawing a bitmap with rounded corners but an entire view. – Jes Sep 20 '11 at 19:27
  • Apparently clipping does not allow for antialiasing. The result is jagged corners. The solution by Ralpheon is the solution. – Sky Kelsey Nov 05 '11 at 20:16
  • 23
    clipPath isn't supported by the hardware accelerated mode used by some devices that are 3.0 and higher. Expect exceptions if you run this code with hardware acceleration on (and you really want that setting turned on for any phone that supports it) – haseman Jan 04 '12 at 21:15
  • What can we do instead? (Without converting to bitmap first, seems like there are no options with vector drawables) – Oliver Dixon May 03 '12 at 11:18
  • 10
    The issue with this approach is that even in software clipPath does not support antialiasing. – Romain Guy Dec 19 '12 at 09:46
  • @RomainGuy's tutorial is here: http://www.curious-creature.org/2012/12/11/android-recipe-1-image-with-rounded-corners/ Lib: https://github.com/vinc3m1/RoundedImageView – greg7gkb May 26 '14 at 23:59
  • This solution was faster than using a bitmap mask. Hence this is my chosen solution. Thanks! – rubmz Dec 07 '16 at 17:23
17

Romain Guy himself writes about this in his blog:

To generate the rounded images I simply wrote a custom Drawable that draws a rounded rectangle using Canvas.drawRoundRect(). The trick is to use a Paint with a BitmapShader to fill the rounded rectangle with a texture instead of a simple color. Here is what the code looks like:

BitmapShader shader;
shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(shader);

RectF rect = new RectF(0.0f, 0.0f, width, height);

// rect contains the bounds of the shape
// radius is the radius in pixels of the rounded corners
// paint contains the shader that will texture the shape
canvas.drawRoundRect(rect, radius, radius, paint);

The sample application goes a little further and fakes a vignette effect by combining the BitmapShader with a RadialGradient.

Dheeraj Vepakomma
  • 26,870
  • 17
  • 81
  • 104
8

Here's a way I discovered to do it with an ImageView. I tried other methods, including the answers here and on similar questions, but I found that they didn't work well for me, as I needed the corners to be applied to the image view and not directly to the bitmap. Applying directly to the bitmap won't work if you're scaling/cropping/panning that bitmap, since the corners will also be scaled/cropped/panned.

public class RoundedCornersImageView extends ImageView {
    private final Paint restorePaint = new Paint();
    private final Paint maskXferPaint = new Paint();
    private final Paint canvasPaint = new Paint();

    private final Rect bounds = new Rect();
    private final RectF boundsf = new RectF();

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

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

    public RoundedCornersImageView(Context context) {
        super(context);
        init();
    }

    private void init() {
        canvasPaint.setAntiAlias(true);
        canvasPaint.setColor(Color.argb(255, 255, 255, 255));
        restorePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
        maskXferPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.getClipBounds(bounds);
        boundsf.set(bounds);

        canvas.saveLayer(boundsf, restorePaint, Canvas.ALL_SAVE_FLAG);
        super.onDraw(canvas);

        canvas.saveLayer(boundsf, maskXferPaint, Canvas.ALL_SAVE_FLAG);
        canvas.drawARGB(0, 0, 0, 0);
        canvas.drawRoundRect(boundsf, 75, 75, canvasPaint);

        canvas.restore();
        canvas.restore();
    }
}

Here's an alternative that uses hardware layers for the final layer composite:

public class RoundedCornersImageView extends ImageView {
    private final Paint restorePaint = new Paint();
    private final Paint maskXferPaint = new Paint();
    private final Paint canvasPaint = new Paint();

    private final Rect bounds = new Rect();
    private final RectF boundsf = new RectF();

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

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

    public RoundedCornersImageView(Context context) {
        super(context);
        init();
    }

    private void init() {
        canvasPaint.setAntiAlias(true);
        canvasPaint.setColor(Color.argb(255, 255, 255, 255));
        restorePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
        maskXferPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));

        setLayerType(View.LAYER_TYPE_HARDWARE, restorePaint);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.getClipBounds(bounds);
        boundsf.set(bounds);

        super.onDraw(canvas);

        canvas.saveLayer(boundsf, maskXferPaint, Canvas.ALL_SAVE_FLAG);
        canvas.drawARGB(0, 0, 0, 0);
        canvas.drawRoundRect(boundsf, 75, 75, canvasPaint);

        canvas.restore();
    }
}

At first I wasn't able to get it to work with this method because my corners were becoming black; I later realized what the problem was after reading this question: Android how to apply mask on ImageView?. It turns out that modifying the alpha in the canvas is actually "scratching it out" directly on the screen, and punching a hole to the underlying window which is black. That's why two layers are needed: one to apply the mask, and another to apply the composited image to the screen.

Community
  • 1
  • 1
Learn OpenGL ES
  • 4,759
  • 1
  • 36
  • 38
  • 1
    The hardware layer solution is way smoother in terms of performance. And it works perfectly on emulators. – Nikolai Mar 03 '19 at 21:47
5

How about creating a NinePatchDrawable image that has just rounded corners and has a transparent body. Overlay your image with an appropriately re-sized version of your NinePatchDrawable.

Navin
  • 1,401
  • 10
  • 16
  • 1
    Very good idea, but what if my rounded-corners image is placed over a gradient background? Would this still work? – iamkoa Nov 10 '09 at 08:13
  • 1
    I agree that this is the less hack-ish way to achieve this. You can simply put the image and the image frame into a FrameLayout (in this order) and you're done. – mxk Jul 28 '10 at 09:37
  • @iamkoa: Make sure the blank parts of your rounded-corners image are transparent. If you're using a gradient in your corners, then use the pixel's alpha channel. (I use Gimp and set a gradient to go from transparent to your colour.) – idbrii Dec 03 '10 at 19:15
  • 1
    This would not work for textured images, in fact it would only work for images with gradient, or solid color. – Sky Kelsey Nov 05 '11 at 18:35
4
package com.pkg;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Bitmap.Config;
import android.graphics.PorterDuff.Mode;
import android.os.Bundle;
import android.os.Environment;
import android.widget.ImageView;

public class RoundedImage extends Activity {
    /** Called when the activity is first created. */
    ImageView imag;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        imag=(ImageView)findViewById(R.id.image);

        //ImageView img1=(ImageView)findViewById(R.id.imageView1);
        BitmapFactory.Options bitopt=new BitmapFactory.Options();
        bitopt.inSampleSize=1;
        // String img=Environment.getExternalStorageDirectory().toString();
        // String filepath =Environment.getExternalStorageDirectory().toString();
        String filepath ="/mnt/sdcard/LOST.DIR";
        File imagefile = new File(filepath + "/logo.jpg");
        FileInputStream fis = null;
        try 
        {
        fis = new FileInputStream(imagefile);
        }  
        catch (FileNotFoundException e1)
        {
        // TODO Auto-generated catch block
        e1.printStackTrace();
        }
        Bitmap bi = BitmapFactory.decodeStream(fis);
        if(bi!=null){
            imag.setImageBitmap(getRoundedCornerBitmap(bi));
        }

    }

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

    final int color = 0xff424242;
    final Paint paint = new Paint();
    final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
    final RectF rectF = new RectF(rect);
    final float roundPx = 12;

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

    canvas.drawRoundRect(rectF, roundPx, roundPx, paint);

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

    return output;
    }
}
DarthJDG
  • 16,511
  • 11
  • 49
  • 56
Swati
  • 41
  • 1
  • 1
    but this code doesnt help to get rounded corners when we need them transparent. Do you know how to achieve that? – Thiago Jun 28 '11 at 21:49
0

Here's another rounded ImageView implementation using Path. The performance is great, but in certain conditions some bugs may appear on emulators because of the hardware drawing.

public class RoundImageView extends ImageView {

    private Path mPath;
    private RectF mRect;
    private Paint mPaint;

    private int mCornerRadius;
    private float mImageAlpha;
    private boolean mIsCircular;

    public RoundImageView(Context context) {
        this(context, null);
    }

    public RoundImageView(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.roundImageViewStyle);
    }

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

        TypedArray a = context.obtainStyledAttributes(
                attrs, R.styleable.RoundImageView, defStyle, 0);

        mCornerRadius = a.getDimensionPixelSize(R.styleable.RoundImageView_cornerRadius, 0);
        mIsCircular = a.getBoolean(R.styleable.RoundImageView_isCircular, false);
        mImageAlpha = a.getFloat(R.styleable.RoundImageView_imageAlpha, 1);

        a.recycle();

        setAlpha((int) (mImageAlpha * 255));

        // Avoid expensive off-screen drawing
        setLayerType(LAYER_TYPE_HARDWARE, null);

        mPath = new Path();

        mRect = new RectF();

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mPath.reset();

        if (mIsCircular) {
            float halfWidth = canvas.getWidth() / 2;
            float halfHeight = canvas.getHeight() / 2;
            float radius = Math.max(halfWidth, halfHeight);

            mPath.addCircle(halfWidth, halfHeight, radius, Path.Direction.CW);
        } else {
            mRect.right = canvas.getWidth();
            mRect.bottom = canvas.getHeight();

            mPath.addRoundRect(mRect, mCornerRadius, mCornerRadius, Path.Direction.CW);
        }

        canvas.drawPath(mPath, mPaint);
    }
}

P.S. Learn OpenGL ES provided the best solution. It's very smooth and works on emulators too.

Nikolai
  • 821
  • 3
  • 17
  • 22
0

best solution I found ->

1) create rounded corner drawable. And set to imageview as background.

<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<corners
    android:radius="10dp" /></shape>

2) Then set image view object property of setClipToOutline(true) in java code.

imageview.setClipToOutline(true);

It works like charm

Yogesh Alai
  • 151
  • 1
  • 5