I need to convert solid color Drawable to Bitmap, because I need to use Bitmap in library. How to do that?
This is my ColorDrawable:
val blackColorDrawable = ColorDrawable(ContextCompat.getColor(this, R.color.colorPrimaryDark))
This is how I convert colorDrawable to bitmap
try {
val bitmap = Bitmap.createBitmap(2, 2, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
blackColorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight())
blackColorDrawable.draw(canvas)
return bitmap
} catch (e: Exception) {
e.printStackTrace()
return null
}
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
}
}
In android API 28 view.getDrawingCache() has been deprecated. Is there any newer solution to generate a Bitmap of a particular view in android.
Two ways to get bitmap of view
Using Canvas
Using Pixel Api
Canvas Java
Bitmap getBitmapFromView(View view) {
Bitmap bitmap = Bitmap.createBitmap(
view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888
);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
return bitmap;
}
Bitmap getBitmapFromView(View view,int defaultColor) {
Bitmap bitmap = Bitmap.createBitmap(
view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888
);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(defaultColor);
view.draw(canvas);
return bitmap;
}
Canvas Kotlin
fun getBitmapFromView(view: View): Bitmap {
val bitmap = Bitmap.createBitmap(
view.width, view.height, Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
view.draw(canvas)
return bitmap
}
fun getBitmapFromView(view: View, defaultColor: Int): Bitmap {
val bitmap = Bitmap.createBitmap(
view.width, view.height, Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
canvas.drawColor(defaultColor)
view.draw(canvas)
return bitmap
}
Example
//#param rootView is View object which you want to get bitmap
Bitmap bitmap = getBitmapFromView(rootView);
Bitmap bitmapColored = getBitmapFromView(rootView,Color.WHITE);
PixelCopy Api
https://stackoverflow.com/a/52985554/9909365
fun getBitmapFromView(view: View, activity: Activity, callback: (Bitmap) -> Unit) {
activity.window?.let { window ->
val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
val locationOfViewInWindow = IntArray(2)
view.getLocationInWindow(locationOfViewInWindow)
try {
PixelCopy.request(window, Rect(locationOfViewInWindow[0], locationOfViewInWindow[1], locationOfViewInWindow[0] + view.width, locationOfViewInWindow[1] + view.height), bitmap, { copyResult ->
if (copyResult == PixelCopy.SUCCESS) {
callback(bitmap)
}
// possible to handle other result codes ...
}, Handler())
} catch (e: IllegalArgumentException) {
// PixelCopy may throw IllegalArgumentException, make sure to handle it
e.printStackTrace()
}
}
}
For More See About
Bitmaps
Canvas
I have found a way to use PixelCopy API for retrieving the view as a Bitmap. Used Kotlin
fun getBitmapFromView(view: View, activity: Activity, callback: (Bitmap) -> Unit) {
activity.window?.let { window ->
val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
val locationOfViewInWindow = IntArray(2)
view.getLocationInWindow(locationOfViewInWindow)
try {
PixelCopy.request(window, Rect(locationOfViewInWindow[0], locationOfViewInWindow[1], locationOfViewInWindow[0] + view.width, locationOfViewInWindow[1] + view.height), bitmap, { copyResult ->
if (copyResult == PixelCopy.SUCCESS) {
callback(bitmap)
}
// possible to handle other result codes ...
}, Handler())
} catch (e: IllegalArgumentException) {
// PixelCopy may throw IllegalArgumentException, make sure to handle it
e.printStackTrace()
}
}
}
Try this:
private fun convertViewToDrawable(view: View): Bitmap {
val spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
view.measure(spec, spec)
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
val b = Bitmap.createBitmap(view.measuredWidth, view.measuredHeight,
Bitmap.Config.ARGB_8888)
val c = Canvas(b)
c.translate((-view.scrollX).toFloat(), (-view.scrollY).toFloat())
view.draw(c)
return b
}
As of the official documentation getDrawingCache() you should use the PixelCopy api.
much precise code..
private fun convertViewToDrawable(view: View): Bitmap {
val b = Bitmap.createBitmap(view.measuredWidth, view.measuredHeight,
Bitmap.Config.ARGB_8888)
val c = Canvas(b)
c.translate((-view.scrollX).toFloat(), (-view.scrollY).toFloat())
view.draw(c)
return b
}
Some other kotlin code (ScrollView case)
val ScrollView.bitmap: Bitmap
get() {
val bitmap = Bitmap.createBitmap(width, getChildAt(0).height, Bitmap.Config.RGB_565)
with(Canvas(bitmap)) {
val background = background
if (background != null) {
background.draw(this)
} else {
drawColor(Color.WHITE)
}
draw(this)
}
return bitmap
}
bitmap = view.drawToBitmap()
val pixel = bitmap.getpixel(x,y)
Kotlin code with view inflation:
fun getBitmapFromView(layoutInflater: LayoutInflater): Bitmap =
layoutInflater.inflate(R.layout.layoutID, null).run {
//Bind data If you needed Example - [findViewById<TextView>(R.id.viewId).text = data]
measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
layout(0, 0, measuredWidth, measuredHeight)
val bitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
draw(canvas)
bitmap
}
layoutInflater can be retrieved like,
Inside activity or fragment -
this.layoutInflater
Outside activity or fragment -
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
How can we achieve a map marker icon with vector asset file, the way google shows it like this, programatically:
Update:
map.addMarker(new MarkerOptions()
.position(latLng)
.icon(BitmapDescriptorFactory.fromResource(R.drawable.your_vector_asset))
.title(title);
this doesnot work when dealing with vector assets. The main reason to ask the question. The error with the above code:
java.lang.IllegalArgumentException: Failed to decode image. The provided image must be a Bitmap.
You can use this method:
private BitmapDescriptor bitmapDescriptorFromVector(Context context, int vectorResId) {
Drawable vectorDrawable = ContextCompat.getDrawable(context, vectorResId);
vectorDrawable.setBounds(0, 0, vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight());
Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
vectorDrawable.draw(canvas);
return BitmapDescriptorFactory.fromBitmap(bitmap);
}
So your code will look like:
map.addMarker(new MarkerOptions()
.position(latLng)
.icon(bitmapDescriptorFromVector(getActivity(), R.drawable.your_vector_asset))
.title(title);
Edit:
In Kotlin it may look like:
private fun bitmapDescriptorFromVector(context: Context, vectorResId: Int): BitmapDescriptor? {
return ContextCompat.getDrawable(context, vectorResId)?.run {
setBounds(0, 0, intrinsicWidth, intrinsicHeight)
val bitmap = Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888)
draw(Canvas(bitmap))
BitmapDescriptorFactory.fromBitmap(bitmap)
}
}
I was looking for exact same requirement, and seeing this question made me happy at first, but same as #Shuddh I wasn't happy with the given answers.
To make my story short, I am using following code for this requirement:
private BitmapDescriptor bitmapDescriptorFromVector(Context context, #DrawableRes int vectorDrawableResourceId) {
Drawable background = ContextCompat.getDrawable(context, R.drawable.ic_map_pin_filled_blue_48dp);
background.setBounds(0, 0, background.getIntrinsicWidth(), background.getIntrinsicHeight());
Drawable vectorDrawable = ContextCompat.getDrawable(context, vectorDrawableResourceId);
vectorDrawable.setBounds(40, 20, vectorDrawable.getIntrinsicWidth() + 40, vectorDrawable.getIntrinsicHeight() + 20);
Bitmap bitmap = Bitmap.createBitmap(background.getIntrinsicWidth(), background.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
background.draw(canvas);
vectorDrawable.draw(canvas);
return BitmapDescriptorFactory.fromBitmap(bitmap);
}
and a usage example:
.icon(bitmapDescriptorFromVector(this, R.drawable.ic_car_white_24dp));
Note: you may wish to use different bounding for your vectors, my vectors were 24dp in size and I've used a 48dp png image(blue-part, which can be a vector too) as background.
UPDATE: Adding screenshot as it was requested.
Here is the code for kotlin lovers ;)
private fun bitMapFromVector(vectorResID:Int):BitmapDescriptor {
val vectorDrawable=ContextCompat.getDrawable(context!!,vectorResID)
vectorDrawable!!.setBounds(0,0,vectorDrawable!!.intrinsicWidth,vectorDrawable.intrinsicHeight)
val bitmap=Bitmap.createBitmap(vectorDrawable.intrinsicWidth,vectorDrawable.intrinsicHeight,Bitmap.Config.ARGB_8888)
val canvas=Canvas(bitmap)
vectorDrawable.draw(canvas)
return BitmapDescriptorFactory.fromBitmap(bitmap)
}
Might be a bit late to the game but this works great with Google Maps v2:
public static BitmapDescriptor getBitmapFromVector(#NonNull Context context,
#DrawableRes int vectorResourceId,
#ColorInt int tintColor) {
Drawable vectorDrawable = ResourcesCompat.getDrawable(
context.getResources(), vectorResourceId, null);
if (vectorDrawable == null) {
Log.e(TAG, "Requested vector resource was not found");
return BitmapDescriptorFactory.defaultMarker();
}
Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
DrawableCompat.setTint(vectorDrawable, tintColor);
vectorDrawable.draw(canvas);
return BitmapDescriptorFactory.fromBitmap(bitmap);
}
Initialized as:
locationMarkerIcon = LayoutUtils.getBitmapFromVector(ctx, R.drawable.ic_location_marker,
ContextCompat.getColor(ctx, R.color.marker_color));
Usage:
googleMap.addMarker(MarkerOptions().icon(getMarkerIcon()).position(latLng));
Note: getMarkerIcon() just returns the initialized non null locationMarkerIcon member variable.
Screenshot:
In Kotlin: I used the below code to show the SVG image on Marker. Here, I used no background color / SVG.
fun getBitmapDescriptorFromVector(context: Context, #DrawableRes vectorDrawableResourceId: Int): BitmapDescriptor? {
val vectorDrawable = ContextCompat.getDrawable(context, vectorDrawableResourceId)
val bitmap = Bitmap.createBitmap(vectorDrawable!!.intrinsicWidth, vectorDrawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
vectorDrawable.setBounds(0, 0, canvas.width, canvas.height)
vectorDrawable.draw(canvas)
return BitmapDescriptorFactory.fromBitmap(bitmap)
}
Use like this way:
googleMap?.addMarker(MarkerOptions().position(LatLng(it.latitude!!, it.longitude!!))
.title(it.airLineDetails))?.setIcon(
getBitmapDescriptorFromVector(requireContext(), R.drawable.ic_flight_blue))
Screen Shot:
If someone who is looking for in kotlin here is the method for you :
private fun bitmapDescriptorFromVector(vectorResId:Int):BitmapDescriptor {
var vectorDrawable = ContextCompat.getDrawable(requireContext(), vectorResId);
vectorDrawable!!.setBounds(0, 0, vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight());
var bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
var canvas = Canvas(bitmap);
vectorDrawable.draw(canvas);
return BitmapDescriptorFactory.fromBitmap(bitmap);
}
above method will convert you vector icon to bitmapdescritor
map.addMarker(new MarkerOptions()
.position(latLng)
.icon(bitmapDescriptorFromVector(getActivity(), R.drawable.your_vector_asset))
.title(title)
and this one for setting marker for your map thanks for Leo Droidcoder from his answer only i have converted it to kotlin
convert vector resource to bitmap object and use BitmapDescriptorFactory.fromBitmap(bitmap)
Bitmap bitmap = getBitmapFromVectorDrawable(getContext(),R.drawable.ic_pin);
BitmapDescriptor descriptor =BitmapDescriptorFactory.fromBitmap(bitmap);
MarkerOptions markerOptions = new MarkerOptions();
markerOptions.icon(descriptor);
Bitmap converter:
public static Bitmap getBitmapFromVectorDrawable(Context context, int drawableId) {
Drawable drawable = AppCompatResources.getDrawable(context, drawableId)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
drawable = (DrawableCompat.wrap(drawable)).mutate();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
Try this
MarkerOptions op = new MarkerOptions();
op.position(src_latlng);
Marker origin_marker = googleMap.addMarker(op);
Bitmap bitmap = getBitmap(this,R.drawable.ic_map_marker);
origin_marker.setIcon(BitmapDescriptorFactory.fromBitmap(bitmap));
getBitmap
public Bitmap getBitmap(Context context, int drawableId) {
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
if (drawable instanceof BitmapDrawable) {
return BitmapFactory.decodeResource(context.getResources(), drawableId);
} else if (drawable instanceof VectorDrawable) {
return getBitmap((VectorDrawable) drawable);
} else {
throw new IllegalArgumentException("unsupported drawable type");
}
}
ic_map_marker.xml
<vector android:height="32dp" android:viewportHeight="512.0"
android:viewportWidth="512.0" android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#f32f00" android:pathData="M288,284.8V480l-64,32V284.8c10.3,2.1 21,3.3 32,3.3S277.7,286.9 288,284.8zM384,128c0,70.7 -57.3,128 -128,128c-70.7,0 -128,-57.3 -128,-128S185.3,0 256,0C326.7,0 384,57.3 384,128zM256,64c0,-17.7 -14.3,-32 -32,-32s-32,14.3 -32,32s14.3,32 32,32S256,81.7 256,64z"/>
</vector>
For a Kotlin user.Please check below code.As I ddid in Fragment class.
class MapPinFragment : Fragment() {
private lateinit var googleMap1: GoogleMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_map_pin, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mapView.onCreate(savedInstanceState)
mapView.onResume()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mapView.getMapAsync { googleMap ->
googleMap1 = googleMap as GoogleMap
addCustomMarker()
}
}
private fun addCustomMarker() {
Log.d("addCustomMarker", "addCustomMarker()")
if (googleMap1 == null) {
return
}
// adding a marker on map with image from drawable
googleMap1.addMarker(
MarkerOptions()
.position(LatLng(23.0225 , 72.5714))
.icon(BitmapDescriptorFactory.fromBitmap(getMarkerBitmapFromView()))
)
}
override fun onDestroy() {
super.onDestroy()
if (mapView != null)
mapView.onDestroy()
}
override fun onLowMemory() {
super.onLowMemory()
mapView.onLowMemory()
}
private fun getMarkerBitmapFromView(): Bitmap? {
val customMarkerView: View? = layoutInflater.inflate(R.layout.view_custom_marker, null)
// val markerImageView: ImageView =
// customMarkerView.findViewById<View>(R.id.profile_image) as ImageView
customMarkerView?.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED );
customMarkerView?.layout(0, 0, customMarkerView.measuredWidth, customMarkerView.measuredHeight);
customMarkerView?.buildDrawingCache();
val returnedBitmap = Bitmap.createBitmap(
customMarkerView!!.measuredWidth, customMarkerView.measuredHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(returnedBitmap)
canvas.drawColor(Color.WHITE, PorterDuff.Mode.SRC_IN)
val drawable = customMarkerView.background
drawable?.draw(canvas);
customMarkerView.draw(canvas);
return returnedBitmap;
}
}