Tuesday, April 19, 2016

How to create a partially-rounded-corners-rectangular drawable with center-crop and without creating new bitmap?

Leave a Comment

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.

4 Answers

Answers 1

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.

Answers 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" /> 

Answers 3

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); 

Answers 4

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.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment