I have a set of two functions that i use to bind images to a recyclerview, one is for converting a string (base64) to a bitmap, the other function is to round the corners of said image.
//convert string to bitmap
fun stringToBitMap( encodedString: String): Bitmap? {
println("string to bitmap is being called")
return try {
val encodeByte: ByteArray = Base64.decode(encodedString, Base64.DEFAULT)
BitmapFactory.decodeByteArray(encodeByte, 0, encodeByte.size)
} catch (e: Exception) {
println("Failed to convert string to bitmap")
e.message
null
}
}
//round corners
fun getRoundedCornerBitmap(bitmap: Bitmap, pixels: Int): Bitmap {
println("get rounded corners is being called")
val output = 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)
val rectF = RectF(rect)
val roundPx = pixels.toFloat()
paint.isAntiAlias = true
canvas.drawARGB(0, 0, 0, 0)
paint.color = color
canvas.drawRoundRect(rectF, roundPx, roundPx, paint)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
canvas.drawBitmap(bitmap, rect, rect, paint)
return output
}
and i anotate my final function with BindingAdapter, then i call the function from the xml file
#BindingAdapter("poster")
fun image (view: ImageView, image: String) {
return view.setImageBitmap(stringToBitMap(image)?.let { getRoundedCornerBitmap(it, 10) })
}
it works, but the performance is poor in some devices, im debbugin my app in a low resource phone (samsung SM-J106B) and the spikes of cpu usage are 35% when scrolling fast(my images are not high res, only 400x400), also the recyclerview keeps calling these functions and it makes the scrolling kinda sluggish. So the question is, how can i improve my functions?
pd: im a complete newbie :(
I ended up using the glide like this:
fun poster(view: ImageView, image: String) {
val imageByteArray: ByteArray = Base64.decode(image, Base64.DEFAULT)
val round = RequestOptions
.bitmapTransform(RoundedCorners(14))
Glide.with(view)
.load(imageByteArray)
.apply(round)
.into(view)
}
performance is better now :D
Related
Heyho,
I'm quiet new to Kotlin, but I came across this problem:
I have a library function, that generates an image(QR-Code). Now I'd like to display that image... But I've no idea how. The documentation only explains how to save the image locally. But I'm not really interested in saving it. So I can either get the image as a FileStream or as a ByteArray. Any possibility to display any of these as an Image in the UI?
An Example:
#Composable
fun QrCode(stand: String) {
Text(text = "QR-Code:", fontSize = 16.sp)
//? Image(QRCode(stand).render().getBytes()) // this obviously won't work
}
Any ideas?
In order to display an Image, you can refer this.
#Composable
fun BitmapImage(bitmap: Bitmap) {
Image(
bitmap = bitmap.asImageBitmap(),
contentDescription = "some useful description",
)
}
So the remaining is to find a way to convert your target input into a Bitmap.
If you have ByteArray of the image file, you can refer to this.
fun convertImageByteArrayToBitmap(imageData: ByteArray): Bitmap {
return BitmapFactory.decodeByteArray(imageData, 0, imageData.size)
}
If you just possess the QR String, you can refer this to convert QR String to a Bitmap.
fun encodeAsBitmap(source: String, width: Int, height: Int): Bitmap? {
val result: BitMatrix = try {
MultiFormatWriter().encode(source, BarcodeFormat.QR_CODE, width, height, null)
} catch (e: Exception) {
return null
}
val w = result.width
val h = result.height
val pixels = IntArray(w * h)
for (y in 0 until h) {
val offset = y * w
for (x in 0 until w) {
pixels[offset + x] = if (result[x, y]) Color.BLACK else Color.WHITE
}
}
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
bitmap.setPixels(pixels, 0, width, 0, 0, w, h)
return bitmap
}
And you need to add this dependency implementation 'com.google.zxing:core:3.3.1' when you decide to use the above code.
I have seen a lot of posts on this topic with all solutions being based on Java and not Kotlin. Is there a solution similiar to the one using Java but in Kotlin?
first get a screenshot of the layout:
getViewScreenshot(view: View): Bitmap {
view.setDrawingCacheEnabled(true)
val bitmap = Bitmap.createBitmap(view.getDrawingCache())
view.setDrawingCacheEnabled(false)
return bitmap
}
then blur it with this function:
fun blurBitmap(bitmap: Bitmap, applicationContext: Context): Bitmap {
lateinit var rsContext: RenderScript
try {
// Create the output bitmap
val output = Bitmap.createBitmap(
bitmap.width, bitmap.height, bitmap.config)
// Blur the image
rsContext = RenderScript.create(applicationContext, RenderScript.ContextType.DEBUG)
val inAlloc = Allocation.createFromBitmap(rsContext, bitmap)
val outAlloc = Allocation.createTyped(rsContext, inAlloc.type)
val theIntrinsic = ScriptIntrinsicBlur.create(rsContext, Element.U8_4(rsContext))
theIntrinsic.apply {
setRadius(10f)
theIntrinsic.setInput(inAlloc)
theIntrinsic.forEach(outAlloc)
}
outAlloc.copyTo(output)
return output
} finally {
rsContext.finish()
}
}
Loading an immutable image to canvas crashes with
java.lang.IllegalStateException: Immutable bitmap passed to Canvas constructor
both in classic Android Canvas and Compose Canvas.
Using the snippet below is the cause for crash in Jetpack Compose.
val deferredResource: DeferredResource<ImageBitmap> =
loadImageResource(id = R.drawable.landscape2)
deferredResource.resource.resource?.let { imageBitmap ->
val paint = Paint().apply {
style = PaintingStyle.Stroke
strokeWidth = 1f
color = Color(0xffFFEE58)
}
Canvas(image = imageBitmap).drawRect(0f, 0f, 100f, 100f, paint)
}
Which is solved with Bitmap as can be seen here with
Bitmap workingBitmap = Bitmap.createBitmap(chosenFrame);
Bitmap mutableBitmap = workingBitmap.copy(Bitmap.Config.ARGB_8888, true);
Canvas canvas = new Canvas(mutableBitmap);
I can convert ImageBitmap to Android Bitmap using
val bitmap = imageBitmap.asAndroidBitmap().copy(Bitmap.Config.ARGB_8888, true)
Found that it's also possible to convert Bitmap back to ImageBitmap using
val newImageBitmap = bitmap.asImageBitmap()
And as result i get after drawing on that Bitmap with snippet below
val canvas = Canvas(newImageBitmap)
canvas.drawRect(0f, 0f, 200f, 200f, paint = paint)
canvas.drawCircle(
Offset(
newImageBitmap.width / 2 - 75f,
newImageBitmap.height / 2 + 75f
), 150.0f, paint
)
Image(bitmap = newImageBitmap)
Is there a less convoluted way to draw on ImageBitmap with Canvas without converting back and forth between Bitmap and ImageBitmap?
loadImageResource() is using AndroidImageBitmap implementation with Bitmap.decodeResource(resources, drawableId) which requests calling it without options.
This is probably limitation of the compose. You'll probably need to write your own loadingImageResource() that will call your own ImageBitmap implementation with mutable Bitmap.
fun imageFromResource(res: Resources, resId: Int): ImageBitmap {
return MutableAndroidImageBitmap(BitmapFactory.decodeResource(res, resId, BitmapFactory.Options().apply { inMutable = true }))
}
class MutableAndroidImageBitmap(internal val bitmap: Bitmap) : ImageBitmap
Note that drawaing of this will fail since, conversion asAndroidBitmap() checks for implementation of ImageBitmap when drawing the ImageBitmap to the foundation Canvas.
I guess you should stick with the steps you have stated in the question. asImageBitmap() does not convert ImageBitmap to Bitmap it just return the wrapped internal property. Converting Bitmap to ImageBitmap does reading the of the pixel data and creates copy of it.
suspend fun ImageBitmap.mutate(context: CoroutineContext = EmptyCoroutineContext, config: Bitmap.Config) = withContext(context) {
val workingBitmap = asAndroidBitmap() //this is just access to `bitmap` property
val mutableBitmap = workingBitmap.copy(config, true)
workingBitmap.recycle()
mutableBitmap.asImageBitmap()
}
Opened bug on issue tracker https://issuetracker.google.com/issues/177129056
I have a mask bitmap with alpha channel,I want use it to cover origin bitmap and make sure edge feather.I try to use below code,but failed:
AndroidManifest.xml:android:hardwareAccelerated="false"
fun featherEdge(maskBm: Bitmap): Bitmap {
if (maskBm == null) return maskBm
val canvasBmp = Bitmap.createBitmap(maskBm.width, maskBm.height, ARGB_8888)
val canvas = Canvas(canvasBmp)
// canvas.isHardwareAccelerated = false
val paint = Paint()
paint.isAntiAlias = true
paint.isDither = true
paint.maskFilter = BlurMaskFilter(20f, BlurMaskFilter.Blur.NORMAL);
canvas.drawBitmap(maskBm, 0f, 0f, paint)
return canvasBmp
}
result bitmap edge is rough:
what I want result:
Question2:
Because android:hardwareAccelerated="false",many function can't work,how to make android:hardwareAccelerated="false" only to off-Screen canvas?
Thank you.
I want to set a MenuItem in my ActionBar which leads to the user profile page in my app. I would like the icon for that to be his profile picture for which I have the URL and can create a BitMap out of.
The image isn't stored in my project folder or anywhere locally so I can't pick it up from R.drawable.
Can somebody help me with setting a bitmap created with the URL as the MenuItem icon? Thanks for the help!
You could do something like this to set the icon from a bitmap:
myMenuItem.setIcon(new BitmapDrawable(getResources(), myBitmap));
In your code this would looks a bit like this:
public boolean onCreateOptionsMenu( Menu menu ) {
MenuInflater inflater = getMenuInflater();
inflater.inflate( R.menu.actionbar, menu );
userItem = menu.findItem(R.id.userItem);
Bitmap myBitmap = //get your bitmap
userItem.setIcon(new BitmapDrawable(getResources(), myBitmap));
return menu;
}
You'll need to get the file from the URL and turn it into a Bitmap first. Note that this will be slow, since if you are doing this when your app is started, the user will have to wait until the file is downloaded before the app will be shown. If your icon changes infrequently, I'd recommend caching it on the device and reusing the locally stored copy.
Also check the "Changing the menus at runtime" section here.
I was searching for this as well and recently I found it from another stackoverflow answer which I'm giving reference link This one is for JAVA and I'm adding Kotlin code below.
Here is the code for kotlin code:
imageUser is Url of image in string format.
You should add it your own image url.
Glide.with(this).asBitmap().load(imageUser)
.into(object : SimpleTarget<Bitmap?>(150, 100) {
override fun onResourceReady(
resource: Bitmap,
transition: Transition<in Bitmap?>?
) {
yourItemIcon.setIcon(BitmapDrawable(resources, resource))
}
})
Set your item as below inside the. onCreateOptionsMenu
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
val yourItemIcon = menu!!.findItem(R.id.ic_topic_info)
return true
}
For new users:
You should also add these lines of code to dependencies the build.gradle in app level for adding Glide library in your app.
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
Kotlin - Picasso Solution
extension function
fun com.squareup.picasso.Target.picassoLoad(url: String, resources: Resources): com.squareup.picasso.Target {
Picasso.get().load(url)
.resize(resources.getDimension(R.dimen.menuIconSize).toInt(),
resources.getDimension(R.dimen.menuIconSize).toInt())
.into(this)
return this
}
in your activity (note that you need to keep a strong reference on target to work)
private var target : com.squareup.picasso.Target? = null
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.basemenu, menu)
menu.findItem(R.id.menu_you_want)?.let { menuItem ->
target = object : com.squareup.picasso.Target {
override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
menuItem.setIcon(R.drawable.fallback_image)
}
override fun onBitmapFailed(e: java.lang.Exception?, errorDrawable: Drawable?) {
menuItem.setIcon(R.drawable.fallback_image)
}
override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) {
menuItem.icon = BitmapDrawable(resources, CircleTransform.getCroppedBitmap(bitmap!!))
}
}.picassoLoad(url, resources)
}
return super.onCreateOptionsMenu(menu)
}
and circletransform class
class CircleTransform : Transformation {
private var x: Int = 0
private var y: Int = 0
override fun transform(source: Bitmap): Bitmap {
val size = Math.min(source.width, source.height)
x = (source.width - size) / 2
y = (source.height - size) / 2
val squaredBitmap = Bitmap.createBitmap(source, x, y, size, size)
if (squaredBitmap !== source) source.recycle()
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
val paint = Paint()
val shader = BitmapShader(squaredBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
paint.shader = shader
paint.isAntiAlias = true
val r = size / 2f
canvas.drawCircle(r, r, r, paint)
squaredBitmap.recycle()
return bitmap
}
override fun key() = "circle(x=$x,y=$y)"
companion object {
fun getCroppedBitmap(bitmap: Bitmap): Bitmap {
val output = 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 / 2f, bitmap.height / 2f,
bitmap.width / 2f, paint)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
canvas.drawBitmap(bitmap, rect, rect, paint)
return output
}
}
}