Obtenir un bitmap à partir d'un dessin vectoriel

133

Dans mon application, je dois définir une grande icône pour une notification. LargeIcon doit être un Bitmap, et mes drawables sont des images vectorielles (la nouvelle fonctionnalité d'Android, voir ce lien ) Le problème est que lorsque j'essaye de décoder une ressource qui est une image vectorielle, j'obtiens un null retourné.

Voici l'exemple de code:

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

Dans cet exemple, lorsque je remplace R.drawable.vector_menu_objectifs par une image "normale", un png par exemple, le résultat n'est pas nul (j'obtiens le bon bitmap) Est-ce qu'il me manque quelque chose?

liltof
la source
1
Il y avait un problème similaire, pas de solution mais une solution de contournement: stackoverflow.com/questions/33548447/...
que

Réponses:

232

Vérifié sur l'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;
}

METTRE À JOUR:

Gradle du projet:

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

Gradle du module:

android {
    compileSdkVersion 23
    buildToolsVersion '23.0.3'
    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 23
        vectorDrawables.useSupportLibrary = true
    }
    ...
}
...
Alexey
la source
Je vous remercie. La réponse précédente ne fonctionne pas avec la version sdk à partir du 23
Paha
2
Cela fonctionne très bien pour moi. Exécution sur l'API 15 sans problème
vera
4
AppCompatDrawableManagerest marqué comme étant @RestrictTo(LIBRARY_GROUP)interne et vous ne devez pas l'utiliser (son API peut changer sans préavis). Utilisez ContextCompatplutôt.
mradzinski
ne fonctionne pas Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Bitmap.setHasAlpha(boolean)' on a null object referencesur cette ligneBitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
user25
5
J'ai eu des problèmes avec cette solution. Pour moi, en utilisant: AppCompatResources au lieu de ContextCompat l'a corrigé: Drawable drawable = AppCompatResources.getDrawable (context, drawableId);
Mike T
64

Vous pouvez utiliser la méthode suivante:

@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;
}

que je combine parfois avec:

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
la source
2
j'espère que @liltof revient et marque ceci comme réponse. La seule chose à noter est que les deux méthodes veulent le wrapper targetAPi - mais le studio Android vous le dira.
roberto tomás
1
J'ai passé environ deux jours à essayer de le faire maintenant en pensant à son problème de fichier svg. Je vous remercie!
sparkly_frog
1
ne fonctionne pas Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Bitmap.setHasAlpha(boolean)' on a null object referencesur cette ligneBitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
user25
45

Si vous souhaitez utiliser Android KTX pour Kotlin, vous pouvez utiliser la méthode d'extension Drawable#toBitmap()pour obtenir le même effet que les autres réponses:

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

ou

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

Pour ajouter ceci et d'autres méthodes d'extension utiles, vous devrez ajouter ce qui suit à votre module au niveau build.gradle

repositories {
    google()
}

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

Voir ici pour les dernières instructions pour ajouter la dépendance à votre projet.

Notez que cela fonctionnera pour toute sous-classe de Drawableet si le Drawableest un, BitmapDrawableil sera raccourci pour utiliser le sous-jacent Bitmap.

David Rawson
la source
C'est la solution la plus simple ici pour Kotlin.
Adam Hurwitz
1
Pour moi, cela fonctionne parfaitement bien avec VectorDrawable.
ubuntudroid le
C'est la meilleure option. Androidx par défaut fournit des fonctionnalités
Reshma
27

Sur la base des réponses précédentes, il peut être simplifié comme ça pour correspondre à la fois à VectorDrawable et BitmapDrawable et pour être compatible avec au moins l'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");
    }
}

Ensuite, vous devez ajouter dans votre fichier gradle:

android {
    defaultConfig {
        vectorDrawables.useSupportLibrary = true
    }
}

Sur pré-Lollipop, il utilisera VectorDrawableCompat et sur Lollipop, il utilisera VectorDrawable.

ÉDITER

J'ai modifié la condition suite au commentaire de @ user3109468

Eselfar
la source
1
instance dessinable de VectorDrawable || drawable instanceof VectorDrawableCompat doit avoir les côtés inversés. Résultats dans une classe introuvable lorsque VectorDrawable n'existe pas, lorsque VectorDrawableCompat doit être vérifié en premier car il existe.
Warrick
La vérification de type de VertorDrawable peut être décrite comme(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && drawable instanceof VectorDrawable)
chmin
6

Félicitations à @Alexey

Voici la Kotlinversion utilisant des extensions pourContext

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
}

Exemple d'utilisation dans Activity:

val bitmap = this.getBitmapFromVectorDrawable(R.drawable.ic_done_white_24dp)
Gunhan
la source
1

Testé sur l'API 16 - JellyBean avec 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;
}   
tomasmark79
la source
1

Utilisez le code suivant pour convertir l'image avec le rapport hauteur / largeur correct (par exemple, pour l'icône de notification):

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
la source
0

Si votre vectorimage de intrinsicWidthet intrinsicHeightest petit et que vous essayez d'afficher le bitmap à une grande vue, vous verrez le résultat est flou.

Dans ce cas, vous pouvez fournir une nouvelle largeur / hauteur pour votre bitmap pour obtenir la meilleure image (ou vous pouvez augmenter la taille du vecteur en xml, mais fournir le desireWidthet desireHeightpeut être plus 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
}

J'espère que ça aide

Phan Van Linh
la source
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));
Amine
la source
0

Si vous souhaitez pouvoir mettre à l'échelle votre sortie à une taille de sortie souhaitée, essayez l'extrait de code suivant:

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
la source
0

Cela vous donne le bitmap dans la taille souhaitée. De plus, il permet de conserver ou non la transparence en fonction de chaque image pour de meilleures performances avec celles qui n'en ont pas besoin.

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;
}
Estid Felipe Lozano Reyes
la source