I am trying to draw a border on an image with a squircle mask using a Picasso Transformation. The squircle mask is a VectorDrawable.
I think the easiest and most flexible way to do this is to first draw a larger squircle of a desired border color using canvas.drawPaint. Then draw a smaller squircle using the photo bitmap using canvas.drawBitmap. I can draw them both separately, I can scale the bitmap and draw it with the mask successfully, but any time I try to combine the two it loses the mask on the canvas.drawBitmap call. Any ideas on what I could be doing wrong?
I have tried loads of blend mode options but I don't think that is the issue.
override fun transform(source: Bitmap): Bitmap {
val width = source.width
val height = source.height
val borderWidth = 100
val output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
//Draw a full size, red squircle
val paint = Paint()
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
paint.color = Color.RED
val mask = context.getDrawable(maskID)
mask.setBounds(0, 0, width, height)
mask.draw(canvas)
canvas.drawPaint(paint)
//Draw a masked, scaled down bitmap of the photo on top
val bitmapPaint = Paint()
bitmapPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
val bitmapMask = context.getDrawable(maskID)
bitmapMask.setBounds(borderWidth / 2, borderWidth / 2, width - borderWidth / 2, height - borderWidth / 2)
bitmapMask.draw(canvas)
val sourceDrawable = source.toDrawable(context.resources)
sourceDrawable.setBounds(borderWidth / 2, borderWidth / 2, width - borderWidth / 2, height - borderWidth / 2)
canvas.drawBitmap(sourceDrawable.bitmap, null,
Rect(borderWidth / 2, borderWidth / 2, width - borderWidth / 2, height - borderWidth / 2),
bitmapPaint)
source.recycle()
return output
}
Result:
If I comment out the call to drawBitmap I get this result (which is nearly there!):
UPDATED 27/11/2018
I've solved the problem by drawing the picture on a temp canvas with desired mask, then drawing the result bitmap over the main canvas. The source code and visual result are included here:
MaskTransformation.kt
import android.content.Context
import android.graphics.*
import android.graphics.drawable.BitmapDrawable
import android.support.annotation.DrawableRes
import android.support.v4.content.ContextCompat
import com.squareup.picasso.Transformation
class MaskTransformation(
private val context: Context,
#DrawableRes private val maskID: Int
) : Transformation {
override fun key(): String {
return "mask"
}
override fun transform(source: Bitmap): Bitmap {
val width = source.width
val height = source.height
val borderWidth = 400
val output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
//Draw a full size, red squircle
val paint = Paint()
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
paint.color = Color.RED
val mask = ContextCompat.getDrawable(context, maskID)!!
mask.setBounds(0, 0, width, height)
mask.draw(canvas)
canvas.drawPaint(paint)
//Draw a masked, scaled down bitmap of the photo on top
val maskingPaint = Paint()
maskingPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
val maskDrawable = ContextCompat.getDrawable(context, maskID)!!
maskDrawable.setBounds(borderWidth / 2, borderWidth / 2, width - borderWidth / 2, height - borderWidth / 2)
val overlayBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val overlayCanvas = Canvas(overlayBitmap)
maskDrawable.draw(overlayCanvas)
val pictureBitmap = Bitmap.createBitmap(width - borderWidth, height - borderWidth, Bitmap.Config.ARGB_8888)
val pictureCanvas = Canvas(pictureBitmap)
val sourceDrawable = BitmapDrawable(context.resources, source)
sourceDrawable.setBounds(borderWidth / 2, borderWidth / 2, width - borderWidth / 2, height - borderWidth / 2)
pictureCanvas.drawBitmap(
sourceDrawable.bitmap,
null,
Rect(0, 0, width - borderWidth, height - borderWidth),
Paint()
)
overlayCanvas.drawBitmap(pictureBitmap, (borderWidth / 2).toFloat(), (borderWidth / 2).toFloat(), maskingPaint)
canvas.drawBitmap(overlayBitmap, 0f, 0f, Paint())
source.recycle()
return output
}
}
MainActivity.kt
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Picasso.get()
.load(R.drawable.img_aminography)
.transform(MaskTransformation(this, R.drawable.ic_squircle))
.into(imageView)
}
}
ic_squircle.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="32"
android:viewportHeight="32">
<path android:fillColor="#000000"
android:pathData="M31.2,14.3v3.5c0,9.8,-5.9,13.5,-13.4,13.5h-3.5c-7.7,0,-13.5,-3.4,-13.5,-13.5v-3.5c0,-10.8,6,-13.5,13.5,-13.5h3.5C25.2,0.8,31.2,4.1,31.2,14.3"/>
</vector>
.
Visual Result
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 have getting one problem, when i am convert view to bitmap then resize bitmap it's getting low quality of image using scale.
My code is here,
`fun scaleBitmap(bitmap: Bitmap, wantedWidth: Int, wantedHeight: Int): Bitmap? {
val originalWidth = bitmap.width.toFloat()
val originalHeight = bitmap.height.toFloat()
val output =Bitmap.createBitmap(wantedWidth,wantedHeight,Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
val m = Matrix()
val scalex = wantedWidth / originalWidth
val scaley = wantedHeight / originalHeight
val xTranslation = 0.2f
val yTranslation = (wantedHeight - originalHeight * scaley) / 1.0f
m.postScale(scalex, scaley)
m.postTranslate(xTranslation, yTranslation)
canvas.drawBitmap(bitmap, m, Paint(Paint.FILTER_BITMAP_FLAG))
return output
}`
Am using DantSu library to print receipts but Arabic text is never printed
here is my code
val printer = EscPosPrinter(
deviceConnection,
printerData.printerDpi,
printerData.printerWidthMM,
printerData.printerNbrCharactersPerLine,
EscPosCharsetEncoding("windows-1252", 16)
)
According to this comment on the issue on the library .. we found a solution but not totally correct
private fun getBitmap(text: String): Bitmap? {
val paint = Paint()
paint.setTextSize(28f)
val bounds = Rect()
paint.getTextBounds(text, 0, text.length, bounds)
val width: Int = bounds.width()
val height: Int = bounds.height()
val bitmap = Bitmap.createBitmap(width * 2, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
canvas.drawColor(Color.WHITE)
canvas.drawText(text, 20f, 20f, paint)
return bitmap
}
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>