The problem
If I use the following helper / extension I will get an exception IBitmapDescriptorFactory is not initialized.
I checked Stackoverflow, some of them recommend to create a local property of the factory and than assign it but this does also not work.
Android Studio shows me the icon on the right side, because of this I think the asset has been added correctly.
Source
fun Webcam.toMarkerOptions(): MarkerOptions {
return MarkerOptions()
.title(name)
.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_map_webcam))
.position(coordinate.toLngLat())
}
Source 2 which also crashes
fun MarkerOptions.icon(context: Context, #DrawableRes vectorDrawable: Int): MarkerOptions {
this.icon(ContextCompat.getDrawable(context, vectorDrawable)?.run {
setBounds(0, 0, intrinsicWidth, intrinsicHeight)
val bitmap = Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888)
draw(Canvas(bitmap))
BitmapDescriptorFactory.fromBitmap(bitmap)
})
return this
}
I have created the MapMarker composable method for this which takes resourceId:
#Composable
fun MapMarker(
context: Context,
position: LatLng,
title: String,
snippet: String,
#DrawableRes iconResourceId: Int
) {
val icon = bitmapDescriptor(
context, iconResourceId
)
Marker(
position = position,
title = title,
snippet = snippet,
icon = icon,
)
}
where bitmapDescriptor:
fun bitmapDescriptor(
context: Context,
vectorResId: Int
): BitmapDescriptor? {
// retrieve the actual drawable
val drawable = ContextCompat.getDrawable(context, vectorResId) ?: return null
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
val bm = Bitmap.createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
// draw it onto the bitmap
val canvas = android.graphics.Canvas(bm)
drawable.draw(canvas)
return BitmapDescriptorFactory.fromBitmap(bm)
}
For more details: https://erselankhan.medium.com/jetpack-compose-custom-google-map-marker-erselan-khan-e6e04178a30b
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)
}
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
}
}
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;
}
}
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
}
}
}