Obtendo o Bitmap a partir do desenho vetorial

132

No meu aplicativo, tenho que definir um ícone grande para uma notificação. O LargeIcon deve ser um Bitmap e meus desenhos são imagens vetoriais (o novo recurso do Android, veja este link ). O problema é quando tento decodificar um recurso que é uma imagem vetorial e recebo um nulo.

Aqui está o exemplo de código:

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

Neste exemplo, quando substituo R.drawable.vector_menu_objectifs por uma imagem "normal", um png por exemplo, o resultado não é nulo (recebo o bitmap correto) Há algo que está faltando?

liltof
fonte
1
Teve problema semelhante, não solução, mas uma solução alternativa: stackoverflow.com/questions/33548447/...
do que

Respostas:

231

Verificado na 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;
}

ATUALIZAR:

Nível do projeto:

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

Gradle do módulo:

android {
    compileSdkVersion 23
    buildToolsVersion '23.0.3'
    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 23
        vectorDrawables.useSupportLibrary = true
    }
    ...
}
...
Alexey
fonte
Obrigado. Resposta anterior não está funcionando com a versão sdk a partir de 23
Paha
2
Isso funciona muito bem para mim. Executando na API 15 sem problemas
vera
4
AppCompatDrawableManagerestá marcado como @RestrictTo(LIBRARY_GROUP)interno, e você não deve usá-lo (sua API pode ser alterada sem aviso prévio). Use em ContextCompatvez disso.
Mradzinski
Não funciona Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Bitmap.setHasAlpha(boolean)' on a null object referencenesta linhaBitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
user25
5
Eu tive problemas com esta solução. Para mim, usando: AppCompatResources em vez de ContextCompat, foi corrigido: Drawable drawable = AppCompatResources.getDrawable (context, drawableId);
Mike T
64

Você pode usar o seguinte método:

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

com as quais eu às vezes combino:

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
fonte
2
espero que @liltof volte e marque isso como resposta. A única coisa a notar é que ambos os métodos desejam o wrapper targetAPi - mas o Android Studio dirá isso.
roberto tomás
1
Passei cerca de dois dias tentando fazer isso agora pensando em seu problema de arquivo svg. Obrigado!
sparkly_frog
1
Não funciona Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Bitmap.setHasAlpha(boolean)' on a null object referencenesta linhaBitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
user25
45

Se você deseja usar o Android KTX for Kotlin, pode usar o método de extensão Drawable#toBitmap()para obter o mesmo efeito que as outras respostas:

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

ou

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

Para adicionar esse e outros métodos úteis de extensão, você precisará adicionar o seguinte ao seu módulo build.gradle

repositories {
    google()
}

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

Veja aqui as instruções mais recentes para adicionar a dependência ao seu projeto.

Observe que isso funcionará para qualquer subclasse de Drawablee, se Drawablefor BitmapDrawable, será um atalho para usar o subjacente Bitmap.

David Rawson
fonte
Esta é a solução mais simples aqui para o Kotlin.
Adam Hurwitz
1
Para mim, funciona perfeitamente bem VectorDrawable.
ubuntudroid 24/02
Esta é a melhor opção .. androidx padrão fornece funcionalidade
Reshma
27

Com base nas respostas anteriores, pode ser simplificado assim para corresponder ao VectorDrawable e ao BitmapDrawable e para ser compatível com pelo menos a 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");
    }
}

Então você tem que adicionar no seu arquivo gradle:

android {
    defaultConfig {
        vectorDrawables.useSupportLibrary = true
    }
}

No pré-pirulito, ele usará o VectorDrawableCompat e, no pirulito, usará o VectorDrawable.

EDITAR

Editei a condição após o comentário de @ user3109468

Eselfar
fonte
1
instância desenhável do VectorDrawable || instância drawableof VectorDrawableCompat deve ter os lados trocados. Resultados em uma classe não encontrada quando o VectorDrawable não existe, quando o VectorDrawableCompat deve ser verificado primeiro porque ele existe.
Warrick
a verificação de tipo de VertorDrawable pode ser descrita como(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && drawable instanceof VectorDrawable)
chmin
6

Muitos elogios para @Alexey

Aqui está a Kotlinversão usando extensões paraContext

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
}

Exemplo de uso em Activity:

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

Testado na API 16 - JellyBean com gavetas vetoriais

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
fonte
1

Use o código a seguir para converter a imagem com a proporção correta (por exemplo, para o ícone de notificação):

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
fonte
0

Se a sua vectorimagem intrinsicWidthe intrinsicHeighté pequeno e você tenta exibir o bitmap para um grande ponto de vista, então você vai ver o resultado é borrão.

Nesse caso, você pode fornecer uma nova largura / altura para o seu bitmap para obter a melhor imagem (ou aumentar o tamanho do vetor em xml, mas forneça desireWidthe desireHeightpossa ser mais flexível).

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
}

Espero que ajude

Phan Van Linh
fonte
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));
Amina
fonte
0

Se você deseja dimensionar sua saída para o tamanho de saída desejado, tente o seguinte trecho:

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
fonte
0

Isso fornece o bitmap no tamanho desejado. Além disso, permite manter ou não a transparência, dependendo de cada imagem, para obter melhor desempenho com aqueles que não precisam dela.

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
fonte