Najbardziej wydajny sposób na zmianę rozmiaru bitmap na Androidzie?

Buduję aplikację społecznościową, w której obrazy są wysyłane z serwera do urządzenia. Gdy urządzenie ma mniejsze rozdzielczości ekranu, muszę zmienić rozmiar bitmap na urządzeniu, aby dopasować je do zamierzonych rozmiarów wyświetlacza.

Problem polega na tym, że za pomocą createScaledBitmap powoduje, że po zmianie rozmiaru hordy miniaturek natrafiam na wiele błędów poza pamięcią.

Jaki jest najbardziej wydajny sposób na zmianę rozmiaru bitmap na Androidzie?

Author: Colt McAnlis, 2015-08-20

2 answers

Ta odpowiedź jest podsumowana z wydajnego ładowania dużych bitmap co wyjaśnia, jak używać inSampleSize do ładowania bitmapy w dół wersja.

W szczególności wstępne skalowanie bitmap wyjaśnia szczegóły różnych metody, jak je łączyć i które są najbardziej efektywne pamięciowo.

Istnieją trzy dominujące sposoby zmiany rozmiaru bitmapy na Androidzie, które mają inną pamięć właściwości:

CreateScaledBitmap API

Ten interfejs API pobierze istniejącą bitmapę i utworzy nową bitmapę o dokładnych wymiarach, które wybrałeś.

Na plus możesz uzyskać dokładnie taki Rozmiar obrazu, jakiego szukasz (niezależnie od tego, jak wygląda). Ale minusem jest to, że ten API wymaga istniejącej bitmapy do działania. Oznacza to, że obraz musiałby zostać załadowany, dekodowany i utworzony bitmap, zanim zostanie możliwość stworzenia nowej, mniejszej wersji. Jest to idealne rozwiązanie, jeśli chodzi o uzyskanie dokładnych wymiarów, ale straszne, jeśli chodzi o dodatkowe obciążenie pamięci. W związku z tym, jest to rodzaj łamania umowy dla większości twórców aplikacji, którzy wydają się być świadomi pamięci

Flaga InSampleSize

BitmapFactory.Options posiada właściwość inSampleSize, która zmieni rozmiar obrazu podczas dekodowania, aby uniknąć konieczności dekodowania na tymczasową bitmapę. Ta liczba całkowita użyta tutaj spowoduje załadowanie obrazu o wartości 1 / x Zmniejszony rozmiar. Na przykład ustawienie inSampleSize na 2 zwraca obraz o połowę mniejszy, a ustawienie na 4 zwraca obraz o 1/4 rozmiaru. Zasadniczo rozmiar obrazu zawsze będzie trochę mocy dwóch mniejszych niż rozmiar źródła.

Z perspektywy pamięci, używanie inSampleSize jest naprawdę szybką operacją. Skutecznie dekoduje tylko każdy x-Ty piksel Twojego obrazu na wynikową bitmapę. Istnieją jednak dwa główne problemy z inSampleSize:

  • Nie daje Ci dokładne rozdzielczości . Zmniejsza tylko rozmiar bitmapy o pewną moc 2.

  • Nie produkuje najlepszej jakości resize . Większość filtrów zmiany rozmiaru tworzy dobrze wyglądające obrazy, czytając bloki pikseli, a następnie ważąc je, aby uzyskać dany zmieniony piksel. inSampleSize unika tego wszystkiego, czytając co kilka pikseli. Wynik jest dość wydajny i niska pamięć, ale jakość cierpi.

Jeśli masz do czynienia tylko z zmniejszanie obrazu o jakiś rozmiar pow2 i filtrowanie nie jest problemem, wtedy nie można znaleźć bardziej wydajnej (lub wydajnej) metody niż inSampleSize.

InScaled, inDensity, inTargetDensity flags

Jeśli chcesz przeskalować obraz do wymiaru, który nie jest równy potędze dwóch, musisz inScaled, inDensity i inTargetDensity flagi BitmapOptions. Po ustawieniu znacznika inScaled, system pobierze wartość skalowania, która ma zostać zastosowana do bitmapy przez dzielenie inTargetDensity przez inDensity wartości.

mBitmapOptions.inScaled = true;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth;

// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeResources(getResources(), 
      mImageIDs, mBitmapOptions);

Użycie tej metody zmieni rozmiar obrazu, a także zastosuje do niego "filtr zmiany rozmiaru", czyli efekt końcowy będzie wyglądał lepiej, ponieważ podczas zmiany rozmiaru wzięto pod uwagę dodatkowe obliczenia. Ale uwaga: ten dodatkowy krok filtrowania zajmuje dodatkowy czas przetwarzania {41]} i może szybko sumować duże obrazy, powodując powolne zmiany rozmiaru i dodatkowe alokacje pamięci dla samego filtra.

To ogólnie nie jest dobrym pomysłem stosowanie tej techniki do obrazu, który jest znacznie większy niż pożądany rozmiar, ze względu na dodatkowe koszty filtrowania.

Magiczna Kombinacja

Z punktu widzenia pamięci i wydajności, możesz połączyć te opcje, aby uzyskać najlepsze wyniki. (ustawienie inSampleSize, inScaled, inDensity i inTargetDensity flagi)

inSampleSize najpierw zostanie zastosowany do obrazu, doprowadzając go do następnej potęgi dwóch większej niż docelowy rozmiar. Wtedy, inDensity & inTargetDensity są używane aby przeskalować wynik do dokładnych wymiarów, zastosuj operację filtrowania w celu wyczyszczenia obrazu.

Połączenie tych dwóch operacji jest znacznie szybsze, ponieważ krok inSampleSize zmniejszy liczbę pikseli, które wynikający krok oparty na gęstości będzie musiał zastosować filtr zmiany rozmiaru.

mBitmapOptions.inScaled = true;
mBitmapOptions.inSampleSize = 4;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth * mBitmapOptions.inSampleSize;

// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeFile(fileName, mBitmapOptions);

Jeśli potrzebujesz dopasować obraz do konkretnych wymiarów, i trochę ładniejszego filtrowania, to ta technika jest najlepszym pomostem do uzyskania odpowiedniego rozmiaru, ale wykonanym w szybka operacja o niskim zużyciu pamięci.

Pobieranie wymiarów obrazu

Uzyskanie rozmiaru obrazu bez dekodowania całego obrazu Aby zmienić rozmiar bitmapy, musisz znać przychodzące wymiary. Możesz użyć flagi inJustDecodeBounds, aby pomóc ci uzyskać wymiary obrazu, bez konieczności dekodowania danych pikseli.

// Decode just the boundaries
mBitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fileName, mBitmapOptions);
srcWidth = mBitmapOptions.outWidth;
srcHeight = mBitmapOptions.outHeight;


//now go resize the image to the size you want

Możesz użyć tej flagi, aby najpierw odszyfrować rozmiar, a następnie obliczyć odpowiednie wartości skalowania do celu rozdzielczość.

 156
Author: Colt McAnlis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-27 23:21:28

Chociaż ta odpowiedź jest ładna (i dokładna), jest również bardzo skomplikowana. Zamiast wymyślać na nowo koło, rozważ biblioteki takie jak Glide, Picasso, UIL, Ion , lub wiele innych, które implementują tę złożoną i podatną na błędy logikę dla Ciebie.

[[0]}Sam Colt zaleca nawet przyjrzenie się Glide i Picasso w wstępnym skalowaniu wzorców wydajności Bitmap .

Korzystając z bibliotek, możesz uzyskać każdą wydajność wspomniany w odpowiedzi Colta, ale z znacznie prostszymi API, które działają konsekwentnie we wszystkich wersjach Androida.

 12
Author: Sam Judd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-03 16:30:47