Pobieranie mapy bitowej z grafiki wektorowej

137

W mojej aplikacji muszę ustawić dużą ikonę powiadomienia. LargeIcon musi być mapą bitową, a moje rysunki to obrazy wektorowe (nowa funkcja w Androidzie, zobacz ten link ) Problem polega na tym, że gdy próbuję zdekodować zasób, który jest obrazem wektorowym, zwracana jest wartość null.

Oto przykładowy kod:

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

W tym przykładzie, kiedy zamieniam R.drawable.vector_menu_objectifs na "normalny" obraz, na przykład png, wynik nie jest zerowy (otrzymuję poprawną mapę bitową) Czy czegoś mi brakuje?

liltof
źródło
1
Wystąpił podobny problem, nie rozwiązanie, ale obejście: stackoverflow.com/questions/33548447/ ...
niż

Odpowiedzi:

241

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

AKTUALIZACJA:

Ocena projektu:

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

Klasa modułu:

android {
    compileSdkVersion 23
    buildToolsVersion '23.0.3'
    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 23
        vectorDrawables.useSupportLibrary = true
    }
    ...
}
...
Alexey
źródło
Dziękuję Ci. Poprzednia odpowiedź nie działa z wersją sdk począwszy od 23
Paha
2
To działa świetnie dla mnie. Działa na API 15 bez problemu
vera
4
AppCompatDrawableManagerjest oznaczona jako @RestrictTo(LIBRARY_GROUP)wewnętrzna i nie należy jej używać (jej API może ulec zmianie bez powiadomienia). Użyj ContextCompatzamiast tego.
mradzinski
nie działa Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Bitmap.setHasAlpha(boolean)' on a null object referencena tej liniiBitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
25
5
Miałem problemy z tym rozwiązaniem. U mnie użycie: AppCompatResources zamiast ContextCompat naprawiło to: Drawable drawable = AppCompatResources.getDrawable (context, drawableId);
Mike T
64

Możesz użyć następującej metody:

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

które czasami łączę z:

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
źródło
2
miejmy nadzieję, że @liltof wróci i oznaczy to jako odpowiedź. Należy zauważyć, że obie metody wymagają opakowania targetAPi - ale Android Studio powie ci to.
roberto tomás
1
Spędziłem około dwóch dni próbując to zrobić, teraz myśląc o problemie z plikiem SVG. Dziękuję Ci!
sparkly_frog
1
nie działa Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Bitmap.setHasAlpha(boolean)' on a null object referencena tej liniiBitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
25
45

Jeśli chcesz używać Androida KTX dla Kotlin, możesz użyć metody rozszerzenia, Drawable#toBitmap()aby osiągnąć ten sam efekt, co w przypadku innych odpowiedzi:

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

lub

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

Aby dodać tę i inne przydatne metody rozszerzeń, musisz dodać następujące elementy do poziomu modułu build.gradle

repositories {
    google()
}

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

Zobacz tutaj najnowsze instrukcje dotyczące dodawania zależności do projektu.

Zauważ, że zadziała to dla każdej podklasy, Drawablea jeśli Drawablejest a BitmapDrawable, spowoduje to skrót do użycia bazowego Bitmap.

David Rawson
źródło
To najprostsze rozwiązanie dla Kotlina.
Adam Hurwitz
1
U mnie działa idealnie z VectorDrawable.
ubuntudroid
To najlepsza opcja. Domyślnie androidx zapewnia funkcjonalność
Reshma
28

Opierając się na poprzednich odpowiedziach, można to uprościć w ten sposób, aby dopasować zarówno VectorDrawable, jak i BitmapDrawable i być kompatybilnym z co najmniej 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");
    }
}

Następnie musisz dodać w swoim pliku gradle:

android {
    defaultConfig {
        vectorDrawables.useSupportLibrary = true
    }
}

W wersji pre-Lollipop będzie używać VectorDrawableCompat, a na Lollipop - VectorDrawable.

EDYTOWAĆ

Zmodyfikowałem warunek po komentarzu @ user3109468

EDYCJA 2 (10/2020)

Przynajmniej od API 21 możesz teraz użyć tego zamiast powyższego kodu (nie próbowałem na poprzednich wersjach API):

AppCompatResources.getDrawable(context, R.drawable.your_drawable)
Eselfar
źródło
1
rysowalna instancja VectorDrawable || rysowana instancja VectorDrawableCompat powinna mieć zamienione strony. Wyniki w klasie nie znaleziono, gdy VectorDrawable nie istnieje, gdy VectorDrawableCompat należy najpierw sprawdzić, ponieważ istnieje.
Warrick,
sprawdzanie typu VertorDrawable można opisać jako(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && drawable instanceof VectorDrawable)
chmin.seo
6

Uznanie dla @Alexey

Oto Kotlinwersja używająca rozszerzeń doContext

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
}

Przykładowe użycie w Activity:

val bitmap = this.getBitmapFromVectorDrawable(R.drawable.ic_done_white_24dp)
Gunhan
źródło
1

Testowane na 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;
}   
tomasmark79
źródło
1

Użyj poniższego kodu, aby przekonwertować obraz z prawidłowym współczynnikiem proporcji (np. Dla ikony powiadomienia):

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
źródło
0

Jeśli twój vectorobraz intrinsicWidthi intrinsicHeightjest mały i spróbujesz wyświetlić bitmapę w dużym widoku, zobaczysz, że wynikiem jest rozmycie.

W takim przypadku możesz podać nową szerokość / wysokość dla mapy bitowej, aby uzyskać lepszy obraz (lub możesz zwiększyć rozmiar wektora w xml, ale podaj desireWidthi desireHeightmoże być bardziej elastyczny).

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
}

Mam nadzieję, że to pomoże

Phan Van Linh
źródło
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
źródło
0

Jeśli chcesz mieć możliwość skalowania wydruku do pożądanego rozmiaru, wypróbuj następujący fragment:

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
źródło
0

W ten sposób uzyskasz mapę bitową w żądanym rozmiarze. Ponadto pozwala zachować lub nie przezroczystość w zależności od każdego obrazu, aby uzyskać lepszą wydajność z tymi, które jej nie potrzebują.

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
źródło
0

W przypadku wektora, który można narysować tutaj, podany kubek kodu pomaga nam, ale pamiętaj, że może być pusty, jeśli nie zostanie znaleziony drawable o wartości 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
źródło