Devrath
Devrath

Reputation: 42824

Adding borders for image rounded image android

What i have:: I have a Imageview for which i am making image as a circle using picassso enter image description here

What i what to do:: I want to add a black border for rounded image using my current implementation, how to achieve this without using third party library

Picasso.with(this)
.load("http://i.imgur.com/DvpvklR.png")
.transform(new RoundedTransformation(50, 4))
.resize(100, 100)
.centerCrop().into(imageView1);

RoundedTransformation.java

// enables hardware accelerated rounded corners
// original idea here : http://www.curious-creature.org/2012/12/11/android-recipe-1-image-with-rounded-corners/
public class RoundedTransformation implements com.squareup.picasso.Transformation {
    private final int radius;
    private final int margin;  // dp

    // radius is corner radii in dp
    // margin is the board in dp
    public RoundedTransformation(final int radius, final int margin) {
        this.radius = radius;
        this.margin = margin;
    }

    @Override
    public Bitmap transform(final Bitmap source) {
        final Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));

        Bitmap output = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(output);
        canvas.drawRoundRect(new RectF(margin, margin, source.getWidth() - margin, source.getHeight() - margin), radius, radius, paint);

        if (source != output) {
            source.recycle();
        }

        return output;
    }

    @Override
    public String key() {
        return "rounded";
    }
}

EDIT

public class RoundedTransformation implements com.squareup.picasso.Transformation {
    private final int radius;
    private final int margin;  // dp

    // radius is corner radii in dp
    // margin is the board in dp
    public RoundedTransformation(final int radius, final int margin) {
        this.radius = radius;
        this.margin = margin;
    }

    @Override
    public Bitmap transform(final Bitmap source) {


        final Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));

        Bitmap output = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(output);
        canvas.drawRoundRect(new RectF(margin, margin, source.getWidth() - margin, source.getHeight() - margin), radius, radius, paint);

        if (source != output) {
            source.recycle();
        }

        Paint paint1 = new Paint();      
        paint1.setColor(Color.RED);
        paint1.setStyle(Style.STROKE);
        paint1.setAntiAlias(true);
        paint1.setStrokeWidth(2);
        canvas.drawCircle((source.getWidth() - margin)/2, (source.getHeight() - margin)/2, radius-2, paint1);


        return output;
    }

    @Override
    public String key() {
        return "rounded";
    }
}

enter image description here

Upvotes: 19

Views: 18552

Answers (4)

chatlanin
chatlanin

Reputation: 6105

This is solution for circle and rectangle shapes. Also it's useful not only for Picasso usage, but for general android Canvas-tasks.

I've created them very voluminous and detailed only for yours understanding of processes, shorten as you want.

But I want to clarify a main problem that many people faced with. In android there no possibilities to create inner or outer border - only centered:

enter image description here

And this is the reason why you receive border elements cut off like these:

enter image description here

So there only a single way to recalculate position of border: in case of

  • circle you have to decrease circle radius by HALF OF BORDER WIDTH
  • rectangle you have to 1) shift border lines "inside" by HALF OF BORDER WIDTH; 2) decrease corner radius by HALF OF BORDER WIDTH

These actions will provide you an expected results:

enter image description here

If you're need only code for you border - pick only correspondent drawBorder() method. Here is an example for empty Fragment with two images:

ViewsCroppingAndBorderingTestFragment.kt

class ViewsCroppingAndBorderingTestFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_test_views_cropping_and_bordering, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val urlCircle      = "https://images-na.ssl-images-amazon.com/images/I/71zLQIfmTlL._AC_SX466_.jpg"
        val urlRectRounded = "https://www.gardendesign.com/pictures/images/675x529Max/site_3/helianthus-yellow-flower-pixabay_11863.jpg"

        val ivCircle       = view.findViewById<ImageView>(R.id.ivCircle)
        val ivRectRounded  = view.findViewById<ImageView>(R.id.ivRectRounded)

        val trCircle       = CircleTransformation()
        val trRectRounded  = RectRoundedTransformation()

        Picasso.get().load(urlCircle).transform(trCircle).into(ivCircle)
        Picasso.get().load(urlRectRounded).transform(trRectRounded).into(ivRectRounded)
    }


    class CircleTransformation() : Transformation {

        override fun transform(source: Bitmap): Bitmap {

            val size = Math.min(source.width, source.height)
            val x = (source.width  - size) / 2
            val y = (source.height - size) / 2
            val w = size
            val h = size
            val squaredBitmap = Bitmap.createBitmap(source, x, y, w, h)

            if (squaredBitmap != source) {
                source.recycle()
            }

            val bitmap = Bitmap.createBitmap(w, h, source.config)

            val canvas = Canvas(bitmap)

//          >>  Draw original rectangle image
//            val paint = Paint(Paint.ANTI_ALIAS_FLAG)
//            canvas.drawBitmap(squaredBitmap, 0F, 0F, paint)

            val rImage = w / 2f

            cropWithCircle(squaredBitmap, canvas, rImage)
            squaredBitmap.recycle()


            drawBorder(canvas, rImage)


            return bitmap
        }

        private fun cropWithCircle(bitmapSource: Bitmap, canvas: Canvas, rImage: Float) {

            val shader = BitmapShader(bitmapSource, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)

            val paint         = Paint(Paint.ANTI_ALIAS_FLAG)
            paint.shader      = shader
            paint.isAntiAlias = true

            val xCenter = rImage
            val yCenter = rImage

            canvas.drawCircle(xCenter, yCenter, rImage, paint)
        }

        private fun drawBorder(canvas: Canvas, rImage: Float) {

            val borderWidth = 30F

            val paintBorder         = Paint()
            paintBorder.strokeWidth = borderWidth
            paintBorder.style       = Paint.Style.STROKE
            paintBorder.color       = Color.GREEN
            paintBorder.isAntiAlias = true


            val cCenter = rImage
            val yCenter = rImage

            // ANDROID'S BORDER IS ALWAYS CENTERED.
            //  So have to reduce radius by half of border's width

            val rBorder = rImage - borderWidth / 2

            canvas.drawCircle(cCenter, yCenter, rBorder, paintBorder)
        }

        override fun key(): String {
            return "circle"
        }
    }

    class RectRoundedTransformation() : Transformation {

        override fun transform(source: Bitmap): Bitmap {

            val x = 0
            val y = 0
            val w = source.width
            val h = source.height
            val squaredBitmap = Bitmap.createBitmap(source, x, y, w, h)

            if (squaredBitmap != source) {
                source.recycle()
            }

            val bitmap = Bitmap.createBitmap(w, h, source.config)

            val canvas = Canvas(bitmap)

//          >>  Draw original rectangle image
//            val paint = Paint(Paint.ANTI_ALIAS_FLAG)
//            canvas.drawBitmap(squaredBitmap, 0F, 0F, paint)

            val rCorner = 80F

            cropCorners(squaredBitmap, canvas, rCorner)
            squaredBitmap.recycle()


            drawBorder(canvas, rCorner)


            return bitmap
        }

        private fun cropCorners(bitmapSource: Bitmap, canvas: Canvas, rCorner: Float) {

            val width  = canvas.width
            val height = canvas.height

            val maskBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8)
            val maskCanvas = Canvas(maskBitmap)
            val maskPaint  = Paint(Paint.ANTI_ALIAS_FLAG)
            val maskRect   = RectF(0F, 0F, width.toFloat(), height.toFloat())

            // 1. draw base rect
            maskCanvas.drawRect(maskRect, maskPaint)

            // 2. cut off inner rounded rect, leave corners
            maskPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
            maskCanvas.drawRoundRect(maskRect, rCorner, rCorner, maskPaint)

            // 3. draw original rectangle image
            val paintSource = Paint(Paint.ANTI_ALIAS_FLAG)

            canvas.drawBitmap(bitmapSource, 0f, 0f, paintSource)

            // 4.cut off corners
            val paintCropped = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG)
            paintCropped!!.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)

            canvas.drawBitmap(maskBitmap!!, 0f, 0f, paintCropped)
        }

        private fun drawBorder(canvas: Canvas, rCorner: Float) {

            val borderWidth  = 30F

            val paintBorder         = Paint()
            paintBorder.strokeWidth = borderWidth
            paintBorder.style       = Paint.Style.STROKE
            paintBorder.color       = Color.RED
            paintBorder.isAntiAlias = true


//            // ANDROID'S BORDER IS ALWAYS CENTERED.
//            //  So have to shift every side by half of border's width

            val rectLeft   = 0 + borderWidth / 2
            val rectTop    = 0 + borderWidth / 2
            val rectRight  = canvas.width.toFloat() - borderWidth / 2
            val rectBottom = canvas.height.toFloat() - borderWidth / 2
            val rectF      = RectF(rectLeft, rectTop, rectRight, rectBottom)

//            //  So have to corner reduce radius by half of border's width
            val rectRCorner = rCorner - borderWidth / 2

            canvas.drawRoundRect(rectF, rectRCorner, rectRCorner, paintBorder)
        }

        override fun key(): String {
            return "rectRounded"
        }
    }
}

fragment_test_views_cropping_and_bordering.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/temp"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:paddingLeft="10dp"
    android:paddingRight="10dp"
    android:background="@android:color/darker_gray">


    <ImageView
        android:id="@+id/ivCircle"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_margin="10dp"/>


    <ImageView
        android:id="@+id/ivRectRounded"
        android:layout_width="200dp"
        android:layout_height="160dp"
        android:layout_margin="10dp"/>


</LinearLayout>

Upvotes: 0

T&#244; Minh Tiến
T&#244; Minh Tiến

Reputation: 1177

Answers of BlackBelt and Devrath are great. And if you are looking for a Kotlin version of this class, here it is:

class RoundedBorderTransform(private val radius: Int, private val margin: Int) : com.squareup.picasso.Transformation {

override fun transform(source: Bitmap?): Bitmap {
    val paint = Paint()
    paint.isAntiAlias = true
    paint.shader = BitmapShader(source!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)

    val output = Bitmap.createBitmap(source.width, source.height, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(output)
    canvas.drawCircle((source.width - margin) / 2f, (source.height - margin) / 2f, radius - 2f, paint)

    if (source != output) {
        source.recycle()
    }

    val borderPaint = Paint()
    borderPaint.color = Color.RED
    borderPaint.style = Paint.Style.STROKE
    borderPaint.isAntiAlias = true
    borderPaint.strokeWidth = 2f
    canvas.drawCircle((source.width - margin) / 2f, (source.height - margin) / 2f, radius - 2f, borderPaint)

    return output
}

override fun key(): String {
    return "roundedBorder"
}

}

Upvotes: 1

Devrath
Devrath

Reputation: 42824

Final transformation class, thanks to blackbelt

public class RoundedTransformation implements com.squareup.picasso.Transformation {
    private final int radius;
    private final int margin;  // dp

    // radius is corner radii in dp
    // margin is the board in dp
    public RoundedTransformation(final int radius, final int margin) {
        this.radius = radius;
        this.margin = margin;
    }

    @Override
    public Bitmap transform(final Bitmap source) {


        final Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));

        Bitmap output = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(output);
        canvas.drawCircle((source.getWidth() - margin)/2, (source.getHeight() - margin)/2, radius-2, paint);

        if (source != output) {
            source.recycle();
        }

        Paint paint1 = new Paint();      
        paint1.setColor(Color.RED);
        paint1.setStyle(Style.STROKE);
        paint1.setAntiAlias(true);
        paint1.setStrokeWidth(2);
        canvas.drawCircle((source.getWidth() - margin)/2, (source.getHeight() - margin)/2, radius-2, paint1);


        return output;
    }

    @Override
    public String key() {
        return "rounded";
    }
}

Upvotes: 19

Blackbelt
Blackbelt

Reputation: 157437

you can use drawCircle with another Paint object. For instance:

Paint paint = new Paint();      
paint.setColor(Color.RED);
paint.setStyle(Style.STROKE);
paint.setAntiAlias(true);
paint.setStrokeWidth(2);
canvas.drawCircle((source.getWidth() - margin)/2, (source.getHeight() - margin)/2, radius-2, paint);

Also, instead of using a drawRoundRect to draw a circle, you can use drawCircle

Upvotes: 9

Related Questions