Convert bitmap to Grayscale - android

In my application i want to upload an image to server but i want to convert it to gray scale before i do that.< br />
So i found a function that takes a bitmap and returns a bitmap as Gray scale.
fun getGrayScale(src: Bitmap): Bitmap {
//Custom color matrix to convert to GrayScale
val matrix = floatArrayOf(
0.3f,
0.59f,
0.11f,
0f,
0f,
0.3f,
0.59f,
0.11f,
0f,
0f,
0.3f,
0.59f,
0.11f,
0f,
0f,
0f,
0f,
0f,
1f,
0f
)
val dest = Bitmap.createBitmap(src.width, src.height, Bitmap.Config.RGB_565)
val canvas = Canvas(dest)
val paint = Paint()
val filter = ColorMatrixColorFilter(matrix)
paint.colorFilter = filter
canvas.drawBitmap(src, 0.toFloat(), 0.toFloat(), paint)
return dest
}
That runs successfully but NOT in Android 10, in which i get the following exception
java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: Software rendering doesn't support hardware bitmaps
at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:298)
at androidx.work.impl.utils.SerialExecutor$Task.run(SerialExecutor.java:91)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
Caused by: java.lang.IllegalArgumentException: Software rendering doesn't support hardware bitmaps
at android.graphics.BaseCanvas.onHwBitmapInSwMode(BaseCanvas.java:632)
at android.graphics.BaseCanvas.throwIfHwBitmapInSwMode(BaseCanvas.java:639)
at android.graphics.BaseCanvas.throwIfCannotDraw(BaseCanvas.java:73)
at android.graphics.BaseCanvas.drawBitmap(BaseCanvas.java:113)
at android.graphics.Canvas.drawBitmap(Canvas.java:1540)
at com.example.utilities.ImageUtils.getGrayScale(ImageUtils.kt:64)
at com.example.utilities.FileUtils.imageRefactor(FileUtils.kt:98)
at com.example.workmanager.FileSplitterWorker.doWork(FileSplitterWorker.kt:54)
at androidx.work.CoroutineWorker$startWork$1.invokeSuspend(CoroutineWorker.kt:68)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:740)
I have tried to add hardwareAccelerated as true in my manifest but same happens.
Can i do this with Glide, or with another way?

In Android O they have introduced new Bitmap.Config the Bitmap.Config.HARDWARE which is an actual optimization when it comes to draw the Bitmap to the screen. So since you are using Software rendering that is why you get the error.
Try this:
fun getGrayScaleBitmap(original: Bitmap): Bitmap {
// You have to make the Bitmap mutable when changing the config because there will be a crash
// That only mutable Bitmap's should be allowed to change config.
val bmp = original.copy(Bitmap.Config.ARGB_8888, true)
val bmpGrayscale = Bitmap.createBitmap(bmp.width, bmp.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bmpGrayscale)
val paint = Paint()
val colorMatrix = ColorMatrix()
colorMatrix.setSaturation(0f)
val colorMatrixFilter = ColorMatrixColorFilter(colorMatrix)
paint.colorFilter = colorMatrixFilter
canvas.drawBitmap(bmp, 0F, 0F, paint)
return bmpGrayscale
}
This will replace the Bitmap.Config from Bitmap.Config.HARDWARE to Bitmap.Config.ARGB_8888.
Hope it helps!!!

Related

Jetpack Compose Immutable ImageBitmap to pass to Canvas

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

canvas draw bitmap crash on Android 10 but not on Android 8

I used canvas to draw bitmap and I used two version of android 8 and 10 to test. Somehow android 10 crash and I just dont seems to understand this error message:
java.lang.IllegalArgumentException: software rendering doesnot support hardware bitmaps
Below is my code. The crash is on the this line: canvas.drawBitmap(bitmap1, 0F, 0F, null)
private fun DrawRectTextAndBitmap()
{
val strOwnerName: String = "Owner: " + tvOwnerName.text.toString()
val strCtcName:String = "Contact : " + tvCtcPersonName.text.toString()
val strCtcMobileNo:String ="Mobile : " + tvCtcPersonMobile.text.toString()
val strAddr1 : String ="Drug Allergy : " + tvAddr1.text.toString()
val strAddr2: String = "Food Allergy : " + tvAddr2.text.toString()
val bitmap1 = (imgResult.getDrawable() as BitmapDrawable).getBitmap()
var h = bitmap1.height
var w = bitmap1.width
val newbitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
val canvas = Canvas(newbitmap)
//-- draw background color
canvas.drawColor(Color.GREEN)
//-- draw bitmap onto Canvas:
canvas.drawBitmap(bitmap1, 0F, 0F, null)
//-- create paint-based color
val paint = Paint()
paint.isAntiAlias()
paint.setColor(ContextCompat.getColor(applicationContext, R.color.royalblue))
paint.alpha = 126
val strDisplayAllText: String = strOwnerName
val paintText = Paint(Paint.ANTI_ALIAS_FLAG)
paintText.setTextSize(rectTextSize)
paintText.setColor(ContextCompat.getColor(applicationContext, R.color.Lightgray))
paintText.setStyle(Paint.Style.FILL)
val color1 = Color.parseColor("#f5f5f5")
paintText.setColor(color1)
paintText.setStyle(Paint.Style.FILL)
//-- Draw the Rectangle
if (h > w ) {
val rectangle = Rect(10, rectPortrait_StartPoint, rectPortrait_H, rectPortrait_W)
canvas.drawRect(rectangle, paint)
} else {
val rectangle = Rect(10, rectLandscape_StartPoint, rectLandscape_H, rectLandscape_W)
canvas.drawRect(rectangle, paint)
}
val rectText = Rect()
paintText.getTextBounds(strOwnerName, 0, strDisplayAllText.length, rectText)
if( h > w ) {
canvas.drawText(strOwnerName, 20F, 830F, paintText)
canvas.drawText(strCtcName, 20F, 890F, paintText)
canvas.drawText(strCtcMobileNo, 20F, 950F, paintText)
canvas.drawText(strAddr1, 20F, 1010F, paintText)
canvas.drawText(strAddr2, 20F, 1110F, paintText)
val density = Resources.getSystem().displayMetrics.density
imgResult.layoutParams.height = 240 * density.toInt()
imgResult.layoutParams.width = 180 * density.toInt()
imgResult.requestLayout()
}else{
//--landscape
canvas.drawText(strOwnerName, 20F, 560F, paintText)
canvas.drawText(strCtcName, 20F, 620F, paintText)
canvas.drawText(strCtcMobileNo, 20F, 680F, paintText)
canvas.drawText(strAddr1, 20F, 740F, paintText)
canvas.drawText(strAddr2, 20F, 870F, paintText)
val density = Resources.getSystem().displayMetrics.density
imgResult.layoutParams.width = 200 * density.toInt()
imgResult.layoutParams.height = 200 * density.toInt()
imgResult.requestLayout()
}
runOnUiThread {
imgResult.setImageBitmap(newbitmap)
}
}
Update: I added the following
1) in manifest
I added :
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:hardwareAccelerated="false"
</application>
2 ) in code , I modified for the ImageView
imgResult.setImageBitmap(newbitmap)
imgResult.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
from Canvas class:
private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
onHwBitmapInSwMode();
}
}
therefore make bitmap with config software, or enable hardware acceleration
You should disable hardware acceleration by this tag android:hardwareAccelerated="false", it can be use in Application and Activity;
Or you can use this method for one particular View like
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

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

How to set a Region using a Rect

I'm trying to set a region in this way:
val region = Region()
region.set(Rect(0, 459, 1080, 0))
I also tried:
val region = Rect(5, 459, 1080, 5).toRegion()
Unfortunately, both of them don't work. It seems the region is always 0. In fact, the method region.bounds should return the Rect I've set before, instead it returns Rect(0, 0, 0, 0).
I still don't understand why the method set() of the Region class does not work, but I've found a way to solve the problem. I created an extension function:
fun Rect.toRightRegion() : Region {
val region = Region()
val path = Path()
path.addRect(RectF(this.left.toFloat(), this.top.toFloat(), this.right.toFloat(), this.bottom.toFloat()), Path.Direction.CW)
val rectF = RectF()
path.computeBounds(rectF, true)
region.setPath(path, Region(rectF.left.toInt(), rectF.top.toInt(), rectF.right.toInt(), rectF.bottom.toInt()))
return region
}
You can call this function directly on your Rect:
val region: Region = Rect(0, 459, 1080, 0).toRightRegion()

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.

Categories

Resources