6

Background

I've already seen how to create a drawable that's circular out of a bitmap, and also how to add an outline (AKA stroke) around it, here.

The problem

I can't find out how to do a similar task for rounding only some of the corners of the bitmap, inside the drawable, without creating a new bitmap, and still do it for a center-crop ImageView.

What I've found

This is what I've found, but it does create a new bitmap, and when using it in an imageView with center-crop (source here):

/**
 * Create rounded corner bitmap from original bitmap.
 *
 * @param input                               Original bitmap.
 * @param cornerRadius                        Corner radius in pixel.
 * @param squareTL,squareTR,squareBL,squareBR where to use square corners instead of rounded ones.
 */
public static Bitmap getRoundedCornerBitmap(final Bitmap input, final float cornerRadius, final int w, final int h,
                                            final boolean squareTL, final boolean squareTR, final boolean squareBL, final boolean squareBR) {
    final Bitmap output = Bitmap.createBitmap(w, h, Config.ARGB_8888);
    final Canvas canvas = new Canvas(output);
    final int color = 0xff424242;
    final Rect rect = new Rect(0, 0, w, h);
    final RectF rectF = new RectF(rect);
    // make sure that our rounded corner is scaled appropriately
    Paint paint = new Paint();
    paint.setXfermode(null);
    paint.setAntiAlias(true);
    canvas.drawARGB(0, 0, 0, 0);
    paint.setColor(color);
    canvas.drawRoundRect(rectF, cornerRadius, cornerRadius, paint);
    // draw rectangles over the corners we want to be square
    if (squareTL) 
        canvas.drawRect(0, 0, w / 2, h / 2, paint);
    if (squareTR) 
        canvas.drawRect(w / 2, 0, w, h / 2, paint);
    if (squareBL) 
        canvas.drawRect(0, h / 2, w / 2, h, paint);
    if (squareBR) 
        canvas.drawRect(w / 2, h / 2, w, h, paint);
    paint.setXfermode(PORTER_DUFF_XFERMODE_SRC_IN);
    canvas.drawBitmap(input, 0, 0, paint);
    return output;
}

And, this is what I've found for creating a rounded corners drawable that acts on all corners:

public static class RoundedCornersDrawable extends Drawable {
    private final float mCornerRadius;
    private final RectF mRect = new RectF();
    private final BitmapShader mBitmapShader;
    private final Paint mPaint;

    public RoundedCornersDrawable(final Bitmap bitmap, final float cornerRadius) {
        mCornerRadius = cornerRadius;
        mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP,
                Shader.TileMode.CLAMP);
        mPaint = new Paint();
        mPaint.setAntiAlias(false);
        mPaint.setShader(mBitmapShader);
        mRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
    }

    @Override
    protected void onBoundsChange(final Rect bounds) {
        super.onBoundsChange(bounds);
        mRect.set(0, 0, bounds.width(), bounds.height());
    }

    @Override
    public void draw(final Canvas canvas) {
        canvas.drawRoundRect(mRect, mCornerRadius, mCornerRadius, mPaint);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    @Override
    public void setAlpha(final int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(final ColorFilter cf) {
        mPaint.setColorFilter(cf);
    }
}

But this solution only works well if the imageView shows the content while maintaining the same aspect ratio as the bitmap, and also has its size pre-determined.

The question

How to create a center-cropped drawable, that shows a bitmap, has rounded corners for specific corners, and also be able to show an outline/stroke around it?

I want to do it without creating a new bitmap or extending ImageView. Only use a drawable that has the bitmap as the input.

Community
  • 1
  • 1
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • why didn't you merge my two approaches? see the original thread for a sample code (made just couple of minutes ago so it can have some minor bugs but you have an idea) – pskink Apr 02 '16 at 08:46
  • 1
    @pskink I tried, and it didn't work for me well. Your code (here: http://pastebin.com/CsuDbx3w) still didn't work as I wrote, so I made this post. The outline doesn't let the image to have rounded corners, and even when i use setStrokeWidth(0), not only it shows an outline, but also doesn't let the image to be center-cropped, and also must have constant size (using wrap_content and match_parent won't let the imageView to be shown) – android developer Apr 02 '16 at 10:37
  • "doesn't let the image to have rounded corners" you can have 1, 2, 3 or 4 rounded corners so i dont know what you mean – pskink Apr 02 '16 at 10:42
  • @pskink You missed the part that I wrote "the outline". If there is no outline, the image will have rounded-corners, but when there is, it doesn't. the image looks completely rectangular even thought I've added the floats array, as such: D roundedCornersDrawable = new D(b, new float[]{20, 20, 0, 0, 40, 40, 0, 0}) . The only thing that gets rounded-corners here is the outline itself. When I remove the outline, it still doesn't show well, as I wrote. If you wish, I can show you some screenshots. – android developer Apr 02 '16 at 10:46
  • post screenshots then, http://pasteboard.co/2IWBL5pB.png and http://pasteboard.co/2IWFwYYD.png – pskink Apr 02 '16 at 11:19
  • I think you've tested only squared images as input. Try landscape/portait images too. Here: http://postimg.org/image/v4qlue84n/ . project: http://expirebox.com/download/aac35a5d0d181a08c64ec6a198f19aaa.html – android developer Apr 02 '16 at 13:44
  • like this: http://pasteboard.co/2J8S7Ljd.jpg ? – pskink Apr 02 '16 at 14:30
  • @pskink Seems ok this way. – android developer Apr 02 '16 at 15:03
  • @pskink Can you please share the code? – android developer Apr 02 '16 at 20:33
  • Could you provide some image you'd like to show in this, and the desired size(s) of the ImageView? – Mimmo Grottoli Apr 14 '16 at 12:00
  • @MimmoGrottoli It really doesn't matter which image. It's just important that it will work on any aspect ratio of images. As example, search on Google images "landscape wallpaper" , "smartphone wallpaper". As for desired size, this can change, but let's start from a rectangle of 200dp*100dp . Examples of images:http://wallpaperstock.net/android-green-square-pattern_wallpapers_34255_320x480.jpg http://7-themes.com/data_images/out/73/7021413-android-wallpaper-3d.jpg – android developer Apr 14 '16 at 13:14
  • Having wrap_content as width or height of the view is not a requirement. Isn't it? – Mimmo Grottoli Apr 15 '16 at 09:01
  • @MimmoGrottoli Any combination for ImageView is allowed. Just look at what you get when you use centerCrop on all combinations of width&height, and what I need is rounded-corners for the result of it (and be able to choose which corners to round and also add outline if I wish). – android developer Apr 15 '16 at 14:33

4 Answers4

4

The SMART way is to use the PorterDuff blending mode. It's really simple and slick to create any fancy shading, blending, "crop" effect. you can find a lot of good tutorial about PorterDuff. here a good one.

Carlos
  • 963
  • 9
  • 19
  • Please show some code about how the drawable should work, given the bitmap and the radius to use per corner, and the outline. – android developer Apr 18 '16 at 16:33
  • Did you take a look at the URL? that explains in detail how to work with drawables. – Carlos Apr 18 '16 at 20:39
  • What I see in the link is of a static mask. I request to know how to do it dynamically, in a drawable, with the ability to set an outline with color and width... How do I do this using your method? – android developer Apr 18 '16 at 23:24
  • If you're looking for just out of the box solution without doing custom touches or enhancements, just to get the rounded corners, I will suggest you to go then with RoundedImageView. You can set what corner needs to be rounded, strokes, color. This is the URL https://github.com/vinc3m1/RoundedImageView – Carlos Apr 19 '16 at 12:23
  • This was already suggested in multiple posts here, and as I've written, I'm searching for a RoundedDrawable solution. Looking at their Drawable class, it has flaws: https://github.com/vinc3m1/RoundedImageView/issues/152 – android developer Apr 19 '16 at 19:19
  • Let me ask you this, is your intention to get somebody else full solution, or get the enough information in order to implement it on your own? I have provided you 2 options and you haven't tried them at all. – Carlos Apr 19 '16 at 20:59
  • First solution doesn't show any code, and is of a static mask instead of what I asked about (which is dynamic). Second solution was already proposed and it has bugs so I can't use it. – android developer Apr 19 '16 at 21:55
  • I am just trying to help you, but it seems that you are concerned about the bounty. The first solution it does contain full commented code (IT IS A TUTORIAL!) just take a minute and read it. The second solution is a well-known library that companies are using. Now you say "It has bugs". let me ask you this. Show me a software without bugs. Perfect software does not exist, ask Google, Microsoft or whatever you got in mind. If you are serious developer with all the information that people have responded to your question, you should be able to figure it out on your own. – Carlos Apr 20 '16 at 12:16
  • Again, both solutions aren't relevant as they are not what I ask for: first solution is static. Second solution doesn't work, unless I use their custom ImageView. I'm searching for a drawable solution. – android developer Apr 20 '16 at 18:44
2

I always use this library to achieve what you are looking for. you can round any corner you want and also add stroke.

https://github.com/vinc3m1/RoundedImageView

You can use it or see it's source codes just for inspiration.

EDIT

there is no need to use Image View and make bitmap or drawable yourself and show it in Image View. Just replace Image View with Rounded Image View and it will handle everything for you without any extra work in code ! here is sample :

<com.makeramen.roundedimageview.RoundedImageView
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/imageView1"
    android:scaleType="centerCrop"
    app:riv_corner_radius="8dp"
    app:riv_border_width="2dp"
    app:riv_border_color="#333333"
    app:riv_oval="false" />

In code, just pass any image resource to it or use any Image Loader with it.

RoundedImageView myRoundedImage=(RoundedImageView)findViewById(R.id.imageView1);
myRoundedImage.setImageResource(R.drawable.MY_DRAWABLE);
// OR
ImageLoader.getInstance().displayImage(YOUR_IMAGE_URL, myRoundedImage);

if you want to just make specific corners rounded try this:

 <com.makeramen.roundedimageview.RoundedImageView
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/imageView1"
    android:scaleType="centerCrop"
    app:riv_corner_radius_top_right="8dp"
    app:riv_corner_radius_bottom_right="8dp"
    app:riv_border_width="2dp"
    app:riv_border_color="#333333"
    app:riv_oval="false" />
Omid Heshmatinia
  • 5,089
  • 2
  • 35
  • 50
  • ok, I've tried it, but it doesn't work as I've written. I used a normal ImageView with RoundedDrawable, and still got a bad result : https://github.com/vinc3m1/RoundedImageView/issues/152 – android developer Apr 09 '16 at 15:13
  • Is it important for you to get the rounded drawable or just showing a rounded corner image in your view fulfill your needs? @androiddeveloper – Omid Heshmatinia Apr 10 '16 at 05:52
  • I don't understand the question. I want a rounded drawable (including for specific corners, and also an outline), that will be able to be used in the normal ImageView. – android developer Apr 10 '16 at 06:38
  • i edited my answer, see if it help you now. @androiddeveloper – Omid Heshmatinia Apr 10 '16 at 07:44
  • You wrote that I need to extend ImageView. The question is about Drawable, without extending ImageView. I've edited the question to make it more clear. – android developer Apr 10 '16 at 08:47
1

Well, you can create a new .xml drawable named my_background and paste this code below:

<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle"
    >
    <solid android:color="#00000000"/>
    <corners
        android:bottomLeftRadius="12dp"
        android:bottomRightRadius="12dp"
        android:topLeftRadius="12dp"
        android:topRightRadius="12dp" />
    <stroke
        android:width="1dp"
        android:color="#000000"
        />
</shape>

Then, you set your ImageButton's, ImageView's background to your new drawable, like this:

android:background="@drawable/my_background"
android:scaleType="centerCrop"

or programatically:

myView.setBackground(R.drawable.my_background);

Hope it helps!

EDIT:

To programmatically create a similar drawable, you can use it:

GradientDrawable shape = new GradientDrawable();
    shape.setShape(GradientDrawable.RECTANGLE);
    shape.setCornerRadii(new float[] { 8, 8, 8, 8, 0, 0, 0, 0 });
    shape.setColor(Color.TRANSPARENT);
    shape.setStroke(3, Color.BLACK);
    v.setBackgroundDrawable(shape);
Geraldo Neto
  • 3,670
  • 1
  • 30
  • 33
  • It is a nice solution, but I was asking about a programmatical solution. One that I create the drawable by code, that takes the bitmap as an input, and doesn't force me to set a different background to the imageView. Also, as I see (and correct me if I'm wrong), your solution forces me to set a white color behind, while what I want is a transparent color behind. – android developer Apr 11 '16 at 05:21
1

Ok, here's my try. The only gotcha is that "int corners" is meant to be a set of flags. Such as 0b1111 where each 1 represents a corner to be rounded, and 0 is the opposite. The order is TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT.

example usage first, formatted for readability:

((ImageView) findViewById(R.id.imageView)).setImageDrawable(
    new RoundedRectDrawable(
        getResources(),
        bitmap,
        (float) .15,
        0b1101,
        8,
        Color.YELLOW
    )
);

code:

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.os.SystemClock;

/**
 * Created by mliu on 4/15/16.
 */
public class RoundedRectDrawable extends BitmapDrawable {
private final BitmapShader bitmapShader;
private final Paint p;
private final RectF rect;
private final float borderRadius;
private final float outlineBorderRadius;
private final int w;
private final int h;
private final int corners;
private final int border;
private final int bordercolor;

public RoundedRectDrawable(final Resources resources, final Bitmap bitmap, float borderRadiusSeed, int corners, int borderPX, int borderColor) {
    super(resources, bitmap);
    bitmapShader = new BitmapShader(getBitmap(),
            BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP);
    final Bitmap b = getBitmap();
    p = getPaint();
    p.setAntiAlias(true);
    p.setShader(bitmapShader);
    w = b.getWidth();
    h = b.getHeight();
    rect = new RectF(0,0,w,h);
    borderRadius = borderRadiusSeed * Math.min(w, h);
    border = borderPX;
    this.corners = corners;
    this.bordercolor = borderColor;
    outlineBorderRadius = borderRadiusSeed * Math.min(w+border,h+border);
}

@Override
public void draw(final Canvas canvas) {
    if ((corners&0b1111)==0){
        if (border>0) {
            Paint border = new Paint();
            border.setColor(bordercolor);
            canvas.drawRect(rect, border);
        }
        canvas.drawRect(rect.left + border, rect.top + border, rect.width() - border, rect.height() - border, p);
    } else {
        if (border >0) {
            Paint border = new Paint();
            border.setColor(bordercolor);
            canvas.drawRoundRect(rect, outlineBorderRadius, outlineBorderRadius, border);

            if ((corners & 0b1000) == 0) {
                //top left
                canvas.drawRect(rect.left, rect.top, rect.width() / 2, rect.height() / 2, border);
            }
            if ((corners & 0b0100) == 0) {
                //top right
                canvas.drawRect(rect.width() / 2, rect.top, rect.width(), rect.height() / 2, border);
            }
            if ((corners & 0b0010) == 0) {
                //bottom left
                canvas.drawRect(rect.left, rect.height() / 2, rect.width() / 2, rect.height(), border);
            }
            if ((corners & 0b0001) == 0) {
                //bottom right
                canvas.drawRect(rect.width() / 2, rect.height() / 2, rect.width(), rect.height(), border);
            }
        }
        canvas.drawRoundRect(new RectF(rect.left + border, rect.top + border, rect.width() - border, rect.height() - border), borderRadius, borderRadius, p);
        if ((corners & 0b1000) == 0) {
            //top left
            canvas.drawRect(rect.left + border, rect.top + border, rect.width() / 2, rect.height() / 2, p);
        }
        if ((corners & 0b0100) == 0) {
            //top right
            canvas.drawRect(rect.width() / 2, rect.top + border, rect.width() - border, rect.height() / 2, p);
        }
        if ((corners & 0b0010) == 0) {
            //bottom left
            canvas.drawRect(rect.left + border, rect.height() / 2, rect.width() / 2, rect.height() - border, p);
        }
        if ((corners & 0b0001) == 0) {
            //bottom right
            canvas.drawRect(rect.width() / 2, rect.height() / 2, rect.width() - border, rect.height() - border, p);
        }
    }
}

}

So, this handles the outline first, if needed, then the bitmap. It marks the canvas up with a rounded rect first, then "squares out" each corner you don't want to round. Seems highly inefficient, and probably is, but average case run time before minimal optimizations (corners = 0b0000, 10 canvas.draw calls) takes ~ 200us on a S7. And, that time is SUPER inconsistent based on phone usage. I've gotten as low as 80us and as high as 1.5ms.

NOTES/WARNING: I am BAD at this. This is not optimal. There's probably better answers already here on SO. The subject matter is just a bit uncommon and difficult to search up. I was originally not going to post this, but at time of writing this is still not marked answered, and the library OP did not wish to use due to problems with their Drawable actually use a very similar approach as my terrible solution. So, now I'm less embarrassed to share this. Additionally, though what I posted today was 95% written yesterday, I know I got some of this code from a tutorial or a SO post, but I can't remember who to attribute cause I didn't end up using it in my project. Apologies whoever you are.

Miao Liu
  • 440
  • 4
  • 9
  • I don't know about how well the code works, but it uses "drawRoundRect", which is available only from API 21. I didn't mention it, but I need it to work from API 14. You should try using the other "drawRoundRect" function. Also, can you please make the corners as floats of their radius, instead of using boolean masks ? – android developer Apr 16 '16 at 21:42
  • re: "floats of their radii", you mean you want to specify different radii for each corner? that will not work with this code. Not even with significant alterations. I can throw something together but it'll probably get even uglier. drawRoundedRect api 21 mainly just accepts individual values instead of taking a Rect only as input. So, really you can just add a step wherein you create a Rect with the values in the first 4 fields of the api 21 call, and replace those fields with that Rect. Not on my coding machine atm, will update this at least later. – Miao Liu Apr 17 '16 at 00:16