Related
I can't seem to figure out how can I make an image (such as ImageView) a circle image, inside an app widget. I mean crop it in the form of a circle, like .clipShape(Circle()) in SwiftUI.
I tried some solutions but they won't work in widgets:
ShapeableImageView breaks the widget
Androidx library is not allowed in widget
Any other solutions?
Updated
I have tried doing it programatically from Kotlin, converting the solution from here:
class AppWidgetProvider : HomeWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
appWidgetIds.forEach { widgetId ->
val view = RemoteViews(context.packageName, R.layout.widget_layout)
val pendingIntent = HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java,
Uri.parse("https://example.com"))
view.setOnClickPendingIntent(R.id.widgetRoot, pendingIntent)
val logoBitmap = BitmapFactory.decodeResource(context.resources, R.drawable.app_icon)
view.setImageViewBitmap(R.id.smarthutsLogo, getCroppedBitmap(logoBitmap))
appWidgetManager.updateAppWidget(widgetId, view)
}
}
#Throws(Exception::class)
fun getCroppedBitmap(bitmap: Bitmap): Bitmap {
val output: Bitmap =
Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
val color = -0xbdbdbe
val paint = Paint()
val rect = Rect(0, 0, bitmap.width, bitmap.height)
paint.isAntiAlias = true
canvas.drawARGB(0, 0, 0, 0)
paint.color = color
canvas.drawCircle(
(bitmap.width / 2).toFloat(),
(bitmap.height / 2).toFloat(),
(bitmap.width / 2).toFloat(),
paint
)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
canvas.drawBitmap(bitmap, rect, rect, paint)
return output
}
}
HomeWidgetProvider is from a Flutter plugin, you can find the code for it here.
What happens is that the the image despite being being cropped as a circle it's shown with a white square background.
From this issue, using Android Studio's code converter, I managed to make a solution in Kotlin:
#Throws(Exception::class)
fun getCroppedBitmap(bitmap: Bitmap): Bitmap {
val output: Bitmap =
Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
val paint = Paint()
val rect = Rect(0, 0, bitmap.width, bitmap.height)
paint.isAntiAlias = true
canvas.drawCircle(
(bitmap.width / 2).toFloat(),
(bitmap.height / 2).toFloat(),
(bitmap.width / 2).toFloat(),
paint
)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
canvas.drawBitmap(bitmap, rect, rect, paint)
return output
}
The newly created function can be used inside the widget's onUpdate method as follows:
val view = RemoteViews(context.packageName, R.layout.widget_layout)
val logoBitmap = BitmapFactory.decodeResource(context.resources, R.drawable.app_icon)
view.setImageViewBitmap(R.id.imageView, getCroppedBitmap(logoBitmap))
Be careful not have android:background="#android:color/white" added to your ImageView. If you need to add a background color, add it when creating the bitmap.
I want to draw transparent bitmaps on a background in a game. However, the bitmap is not transparent, it still has white color in the background. Here is my code
inner class Canvass(context: Context) : View(context) {
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val frame_paint: Paint = Paint()
frame_paint.style = Paint.Style.FILL
frame_paint.isAntiAlias = true
val framegame = BitmapFactory.decodeResource (resources, R.drawable.frame)
canvas.drawBitmap (framegame, 0f, 0f, frame_paint)
canvas.drawPath(mPath, mPaint)
val tennis_paint: Paint = Paint()
tennis_paint.style = Paint.Style.FILL
tennis_paint.color = Color.TRANSPARENT
tennis_paint.isAntiAlias = true
val tennis = BitmapFactory.decodeResource (resources, R.drawable.tennis_ball)
canvas.drawBitmap (tennis, 50f, 200f, tennis_paint)
}
}
I found this code, it works for me.
val tennis_paint: Paint = Paint()
tennis_paint.style = Paint.Style.FILL
tennis_paint.isAntiAlias = true
val tennis = BitmapFactory.decodeResource (resources, R.drawable.tennis_ball)
var bitmap = transparent_bitmap(tennis)
canvas.drawBitmap (bitmap, 50f, 300f, tennis_paint)
fun transparent_bitmap(bmp:Bitmap):Bitmap{
var bitmap: Bitmap = Bitmap.createBitmap(
bmp.width,
bmp.height,
Bitmap.Config.ARGB_8888
)
bitmap.setHasAlpha(true)
for (x in 1..bmp.width-1){
for (y in 1..bmp.height-1){
var pixel = bmp.getPixel(x,y)
val redValue = Color.red(pixel)
val blueValue = Color.blue(pixel)
val greenValue = Color.green(pixel)
if ((redValue==255)and(blueValue==255)and(greenValue==255)){
bitmap.setPixel(x,y,Color.argb(1,0,0,0))
}else {
bitmap.setPixel(x,y,Color.rgb(redValue,greenValue,blueValue))
}
}
}
return bitmap
}
simple fun
fun getCircleBitmap(bitmap: Bitmap, recycle: Boolean): Bitmap {
val paint = Paint()
paint.isAntiAlias = true
paint.color = Color.WHITE
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
val rect = Rect(0, 0, bitmap.width, bitmap.height)
val rectF = RectF(rect)
val output = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
canvas.drawARGB(0, 0, 0, 0)
canvas.drawOval(rectF, paint)
canvas.drawBitmap(bitmap, rect, rect, paint)
if (recycle) {
bitmap.recycle()
}
return output
}
Taget API 29. Emulator API 28. Kotlin 1.3.40.
About month ago this fun worked. But now on "canvas.drawBitmap" error "Software rendering doesn't support hardware bitmaps". Try android:hardwareAccelerated="false" - same error. Why?
so, my solution:
fun getCircleBitmapDrawable(bitmap: Bitmap): RoundedBitmapDrawable {
val round = RoundedBitmapDrawableFactory.create(context.resources, bitmap)
round.isCircular = true
round.setAntiAlias(true)
return round
}
What i have:: I have a Imageview for which i am making image as a circle using picassso
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";
}
}
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";
}
}
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
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"
}
}
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:
And this is the reason why you receive border elements cut off like these:
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:
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>
What i have:: I have a Imageview for which i am making image as a circle using picassso
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";
}
}
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";
}
}
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
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"
}
}
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:
And this is the reason why you receive border elements cut off like these:
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:
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>