Jetpack Compose Immutable ImageBitmap to pass to Canvas - android

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

Related

How to fill with color only the icon on canvas,without the background android studio

I want to change the color of my png,i want to fill it with color,so i thought about creating a bitmap from the png,placing it on a canvas and drawing the canvas,but it is drawing the whole canvas,like this,so my icons are covered completely
This is my code:
private fun testResizeImg(imgRes: Int): BitmapDescriptor {
var bm = BitmapFactory.decodeResource(resources, imgRes)
bm = Bitmap.createScaledBitmap(bm, (bm.width * 0.1).toInt(), (bm.height * 0.1).toInt(), true)
val canvas = Canvas(bm)
val paint = Paint()
paint.color = Color.BLUE
canvas.drawPaint(paint)
return BitmapDescriptorFactory.fromBitmap(bm)
}
What is the best approach to do this,i have a png and i want to paint it.
You can try something like that ? (not tested)
You can use theColor Filter to change the icon's color at runtime.
Then you transform the drawable into Bitmap.
private fun testResizeImg(#DrawableRes imgRes: Int): BitmapDescriptor {
val mIcon = ContextCompat.getDrawable(getActivity(), imgRes)
mIcon?.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.BLUE, BlendModeCompat.SRC_ATOP)
var bm : Bitmap = (mIcon as BitmapDrawable).bitmap
bm = Bitmap.createScaledBitmap(bm, (bm.width * 0.1).toInt(), (bm.height * 0.1).toInt(), true)
return BitmapDescriptorFactory.fromBitmap(bm)
}
Note that the #DrawableRes is here to help for the code inspection
private fun testResizeImg(imgRes: Int): BitmapDescriptor {
var bm = BitmapFactory.decodeResource(resources, imgRes)
bm = Bitmap.createScaledBitmap(bm, (bm.width * 0.1).toInt(), (bm.height * 0.1).toInt(), true)
val canvas = Canvas(bm)
canvas.drawBitmap(bm,0F,0F,null)
return BitmapDescriptorFactory.fromBitmap(bm)
}

how can i improve these functions to bind bitmaps on recyclerView?

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

Take screenshot from io.flutter.embedding.android.flutterview

I want to programmatically take screenshots from my FlutterView in Android.
When it was io.flutter.view.FlutterView I just use getBitmap()
But how can I make screenshot from io.flutter.embedding.android.FlutterView ?
I tried following code but this does not work:
private fun getBitmapFromNewEmbeddedFlutterView(flutterView: io.flutter.embedding.android.FlutterView): Bitmap {
val bitmap = Bitmap.createBitmap(
flutterView.width,
flutterView.height,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
flutterView.layout(
0,
0,
flutterView.measuredWidth,
flutterView.measuredHeight
)
flutterView.draw(canvas)
return bitmap
}

Android how to use BlurMaskFilter.Blur.NORMAL to a mask bitmap in Canvas

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.

Native Crash in getting bitmap from resource

I have a native crash:
A/libc: invalid address or address of corrupt block 0x55766f1b00 passed to try_realloc_chunk
A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0xdeadbaad in tid 32219 (onPool-worker-1)
when executing the drawable.draw(canvas) line in the following method:
fun getBitmapFromResource(context: Context, imageRes: Int, iconSize: Float = CATEGORY_ICON_SIZE): Bitmap? {
val drawable = ContextCompat.getDrawable(context, imageRes)
if (drawable is BitmapDrawable) {
return drawable.bitmap
}
val size = GraphicsUtils.toPx(context, iconSize)
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
drawable!!.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas) // crash!!
return bitmap
}
The drawable is VectorDrawable implementation. I am executing this code on a background thread in a coroutine.
I added vectorDrawables.useSupportLibrary = true to build.gradle file, but it did not help.
I need bitmap object because from its width and height I draw a custom chart and I need to perform size calculations there.
I had the suspicion that multi-threading might break the process, so I added this code in the runBlocking section (still on a background thread) - no effect.
Any ideas how to fix this?
After several hours of investigation, I fixed the issue.
The problem seems to be that more than one coroutine was entering the method at the same time. I used Mutex to make sure only one coroutine can be inside the method.
object UIUtilsSingleton {
private val mutex = Mutex()
suspend fun getBitmapFromResource(context: Context, imageRes: Int): Bitmap? {
var bitmap: Bitmap? = null
mutex.withLock {
val iconSize = 42f
val drawable = ContextCompat.getDrawable(context, imageRes)
if (drawable is BitmapDrawable) {
return drawable.bitmap
}
val size = GraphicsUtils.toPx(context, iconSize)
bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
drawable!!.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
}
return bitmap
}
}

Categories

Resources