165

In my application, I have to set a large icon for a notification. LargeIcon must be a Bitmap, and my drawables are vector images (the new feature in Android, see this link) The problem is when I try to decode a resource that is a vector image, I get a null returned.

Here is the sample of code :

if (BitmapFactory.decodeResource(arg0.getResources(), R.drawable.vector_menu_objectifs) == null)
        Log.d("ISNULL", "NULL");
    else
        Log.d("ISNULL", "NOT NULL");

In this sample, when I replace R.drawable.vector_menu_objectifs with a "normal" image, a png for exemple, the result is not null (I get the correct bitmap) Is there something I'm missing?

liltof
  • 2,153
  • 2
  • 14
  • 23
  • 1
    Had similiar issue, not solution but a workaround: http://stackoverflow.com/questions/33548447/vectordrawable-with-googlemap-bitmapdescriptor/33550407#33550407 – Than Nov 13 '15 at 15:57

13 Answers13

281

Checked on API: 17, 21, 23

public static Bitmap getBitmapFromVectorDrawable(Context context, int drawableId) {
    Drawable drawable = ContextCompat.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;
}

UPDATE:

Project gradle:

dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0-alpha5'
    }

Module gradle:

android {
    compileSdkVersion 23
    buildToolsVersion '23.0.3'
    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 23
        vectorDrawables.useSupportLibrary = true
    }
    ...
}
...
Hugo Gresse
  • 17,195
  • 9
  • 77
  • 119
Alexey
  • 4,384
  • 1
  • 26
  • 34
  • 5
    ```AppCompatDrawableManager``` is marked as ```@RestrictTo(LIBRARY_GROUP)``` so it's internal and you should not use it (it's API can change without notice). Use ```ContextCompat``` instead. – mradzinski Feb 11 '17 at 04:35
  • doesn't work `Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Bitmap.setHasAlpha(boolean)' on a null object reference` on this line `Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);` – user25 Apr 30 '17 at 20:10
  • @mradzinski great, it's working. I've edited the answer to better reflect the best solution. – Hugo Gresse Jun 19 '17 at 20:40
  • 6
    I had issues with this solution. For me using: AppCompatResources instead of ContextCompat fixed it: Drawable drawable = AppCompatResources.getDrawable(context, drawableId); – Mike T Aug 22 '17 at 08:01
  • This works great for me. Running on API 28 without a problem – hannes ach Jul 12 '19 at 07:14
  • What if the intrinsic dimensions are -1? – John Glen Oct 01 '20 at 22:43
  • There is an extension function in the `androidx.core.graphics.drawable` package [toBitmap()](https://developer.android.com/reference/kotlin/androidx/core/graphics/drawable/package-summary#tobitmap) – klenki Aug 09 '21 at 14:10
94

If you are willing to use Android KTX for Kotlin you can use the extension method Drawable#toBitmap() to achieve the same effect as the other answers:

val bitmap = AppCompatResources.getDrawable(requireContext(), drawableId).toBitmap() 

or

val bitmap = AppCompatResources.getDrawable(context, drawableId).toBitmap() 

To add this and other useful extension methods you will need to add the following to your module-level build.gradle

repositories {
    google()
}

dependencies {
    implementation "androidx.core:core-ktx:1.2.0"
}

See here for latest instructions for adding the dependency to your project.

Note that this will work for any subclass of Drawable and if the Drawable is a BitmapDrawable it will shortcut to use the underlying Bitmap.

David Rawson
  • 20,912
  • 7
  • 88
  • 124
69

You can use the following method:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static Bitmap getBitmap(VectorDrawable vectorDrawable) {
    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());
    vectorDrawable.draw(canvas);
    return bitmap;
}

which I sometimes combine with:

private static Bitmap getBitmap(Context context, int drawableId) {
    Drawable drawable = ContextCompat.getDrawable(context, drawableId);
    if (drawable instanceof BitmapDrawable) {
        return ((BitmapDrawable) drawable).getBitmap();
    } else if (drawable instanceof VectorDrawable) {
        return getBitmap((VectorDrawable) drawable);
    } else {
        throw new IllegalArgumentException("unsupported drawable type");
    }
}
snodnipper
  • 2,580
  • 1
  • 29
  • 19
  • 2
    hopefully @liltof comes back and marks this as answer. The one thing to note is that both methods want the targetAPi wrapper — but android studio will tell you that. – roberto tomás Mar 24 '16 at 12:49
  • 1
    I've spent about two days trying to do this now thinking its svg file issue. Thank you! – El_o.di Aug 18 '16 at 12:29
  • 1
    doesn't work `Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Bitmap.setHasAlpha(boolean)' on a null object reference` on this line `Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);` – user25 Apr 30 '17 at 20:21
30

Based on the previous answers it can be simplified like that to match both VectorDrawable and BitmapDrawable and to be compatible with at least API 15.

public static Bitmap getBitmapFromDrawable(Context context, @DrawableRes int drawableId) {
    Drawable drawable = AppCompatResources.getDrawable(context, drawableId);

    if (drawable instanceof BitmapDrawable) {
        return ((BitmapDrawable) drawable).getBitmap();
    } else if (drawable instanceof VectorDrawableCompat || drawable instanceof VectorDrawable) {
        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;
    } else {
        throw new IllegalArgumentException("unsupported drawable type");
    }
}

Then you have to add in your gradle file:

android {
    defaultConfig {
        vectorDrawables.useSupportLibrary = true
    }
}

On pre-Lollipop it will use VectorDrawableCompat and on Lollipop it will use VectorDrawable.

EDIT

I've edited the condition following the comment of @user3109468

EDIT 2 (10/2020)

At least from API 21 you can now use this instead of the above code (I haven't tried on previous API versions):

AppCompatResources.getDrawable(context, R.drawable.your_drawable)
Eselfar
  • 3,759
  • 3
  • 23
  • 43
  • 1
    drawable instanceof VectorDrawable || drawable instanceof VectorDrawableCompat should have the sides swapped. Results in a Class Not Found when VectorDrawable doesn't exist, when VectorDrawableCompat should be checked first because it does exist. – Warrick Jun 14 '17 at 23:20
  • type checking of VertorDrawable can be described as `(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && drawable instanceof VectorDrawable)` – chmin.seo Nov 30 '19 at 10:25
7

Kudos to @Alexey

Here is the Kotlin version using extensions to Context

fun Context.getBitmapFromVectorDrawable(drawableId: Int): Bitmap? {
    var drawable = ContextCompat.getDrawable(this, drawableId) ?: return null

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        drawable = DrawableCompat.wrap(drawable).mutate()
    }

    val bitmap = Bitmap.createBitmap(
            drawable.intrinsicWidth,
            drawable.intrinsicHeight,
            Bitmap.Config.ARGB_8888) ?: return null
    val canvas = Canvas(bitmap)
    drawable.setBounds(0, 0, canvas.width, canvas.height)
    drawable.draw(canvas)

    return bitmap
}

Example usage in Activity:

val bitmap = this.getBitmapFromVectorDrawable(R.drawable.ic_done_white_24dp)
Gunhan
  • 6,807
  • 3
  • 43
  • 37
1

Tested on API 16 - JellyBean with Vector Drawables

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;
}   
James Douglas
  • 3,328
  • 2
  • 22
  • 43
1

Use the following code to convert image with the correct aspect ratio (e.g., for notification icon):

public static Bitmap getBitmapFromVector(Context context, int drawableId) {
    Drawable drawable = ContextCompat.getDrawable(context, drawableId);
    int width = drawable.getIntrinsicWidth();
    int height = drawable.getIntrinsicHeight();
    Bitmap bitmap;
    if (width < height) {    //make a square
        bitmap = Bitmap.createBitmap(height, height, Bitmap.Config.ARGB_8888);
    } else {
        bitmap = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888);
    }
    Canvas canvas = new Canvas(bitmap);
    drawable.setBounds(0, 0,
            drawable.getIntrinsicWidth(),    //use dimensions of Drawable
            drawable.getIntrinsicHeight()
    );
    drawable.draw(canvas);
    return bitmap;
}
antaki93
  • 704
  • 7
  • 10
1

For the vector drawable here given cup of code help us, but remember it might be null if drawable is not found of NULL

@Nullable
public static Bitmap drawableToBitmap(Context context, int drawableId) {
    Drawable drawable = ContextCompat.getDrawable(context, drawableId);
    if (drawable != null) {
        int width = drawable.getIntrinsicWidth();
        int height = drawable.getIntrinsicHeight();
        Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bmp);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);
        return bmp;
    }
    return null;
}
Kishan Donga
  • 2,851
  • 2
  • 23
  • 35
0
Drawable layerDrawable = (Drawable) imageBase.getDrawable();
Bitmap bitmap = Bitmap.createBitmap(layerDrawable.getIntrinsicWidth(),
        layerDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
layerDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
layerDrawable.draw(canvas);  
imageTeste.setImageBitmap(addGradient(bitmap));
Ivan
  • 4,186
  • 5
  • 39
  • 72
Amine
  • 9
  • 1
  • 1
0

If your vector image intrinsicWidth and intrinsicHeight is small and you try to display the bitmap to a big view, then you will see the result is blur.

In that case, you can provide a new width/height for your bitmap to get the better image (or you can increase the vector size in xml, but provide the desireWidth and desireHeight may be more flexible).

private fun getBitmap(drawableId: Int, desireWidth: Int? = null, desireHeight: Int? = null): Bitmap? {
    val drawable = AppCompatResources.getDrawable(context, drawableId) ?: return null
    val bitmap = Bitmap.createBitmap(
        desireWidth ?: drawable.intrinsicWidth,
        desireHeight ?: drawable.intrinsicHeight,
        Bitmap.Config.ARGB_8888
    )
    val canvas = Canvas(bitmap)
    drawable.setBounds(0, 0, canvas.width, canvas.height)
    drawable.draw(canvas)
    return bitmap
}

Hope it help

Linh
  • 57,942
  • 23
  • 262
  • 279
0

If you want to be able to scale your output to a desired output size, try the following snippet:

fun getBitmapFromVectorDrawable(context: Context, drawableId: Int, outputSize: OutputSize? = null): Bitmap? {
    var drawable = ContextCompat.getDrawable(context, drawableId) ?: return null
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        drawable = DrawableCompat.wrap(drawable).mutate()
    }

    var targetBitmap: Bitmap
    if (outputSize != null) {
        targetBitmap = Bitmap.createBitmap(outputSize.width,
                outputSize.height, Bitmap.Config.ARGB_8888)
    } else {
        targetBitmap = Bitmap.createBitmap(drawable.intrinsicWidth,
            drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
    }

    val canvas = Canvas(targetBitmap)
    val scaleX =  targetBitmap.width.toFloat()/drawable.intrinsicWidth.toFloat()
    val scaleY =  targetBitmap.height.toFloat()/drawable.intrinsicHeight.toFloat()
    canvas.scale(scaleX, scaleY)
    drawable.draw(canvas)

    return targetBitmap
}

class OutputSize(val width: Int, val height: Int)
Hans
  • 1,886
  • 24
  • 18
0

This gives you the bitmap in the size you want. In addition, it allows you to maintain or not transparency depending on each image for better performance with those that do not need it.

public static Bitmap drawableToBitmap(Resources res, int drawableId,
        int width, int height, boolean keepAlpha) {
    Drawable drawable = res.getDrawable(drawableId);
    Bitmap bmp = createBitmap(width, height, keepAlpha ?
            Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
    Canvas cvs = new Canvas(bmp);
    drawable.setBounds(0, 0, width, height);
    drawable.draw(cvs);
    return bmp;
}
0

Create separate fun of Vector to Bitmap

 //vectorToBitmapMarker
    private fun fromVectorToBitmap(id: Int, color: Int): BitmapDescriptor
    {
        val vectorDrawable: Drawable? = ResourcesCompat.getDrawable(resources, id, null)

        if (vectorDrawable == null)
        {
            d("VTOB","Resource not found!")
            return BitmapDescriptorFactory.defaultMarker()
        }

        val bitmap = Bitmap.createBitmap(
            vectorDrawable.intrinsicWidth,
            vectorDrawable.intrinsicHeight,
            Bitmap.Config.ARGB_8888)

        val canvas = Canvas(bitmap)
        vectorDrawable.setBounds(0,0,canvas.width, canvas.height)
        DrawableCompat.setTint(vectorDrawable, color)
        vectorDrawable.draw(canvas)

        return BitmapDescriptorFactory.fromBitmap(bitmap)

    }

now do changes in onMapReady() -> .icon()

mMap.addMarker(
       MarkerOptions().position(goa)
           .title("Marker in Goa")
           .draggable(true)
           .icon(fromVectorToBitmap(R.drawable.location, Color.parseColor("#FF0560"))))