2015-08-20 11 views
107

Buduję intensywną graficznie 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świetlania.Najbardziej wydajny sposób na zmianę rozmiaru map bitowych na systemie Android?

Problem polega na tym, że użycie createScaledBitmap powoduje, że po zmianie rozmiaru hordy miniaturek pojawia się wiele błędów związanych z brakami w pamięci.

Jaki jest najbardziej wydajny sposób na zmianę rozmiaru map bitowych w systemie Android?

+7

Czy twój serwer nie może wysłać prawidłowego rozmiaru, aby zapisać pamięć RAM klienta i przepustowość !? – James

+2

Jest to ważne tylko wtedy, gdy posiadałem zasób serwera, miał on dostępny komponent obliczeniowy i we wszystkich przypadkach mógł przewidzieć dokładne wymiary obrazów dla współczynników proporcji, których jeszcze nie widział. Więc jeśli ładujesz zawartość zasobów z CDN innej firmy (jak ja) to nie działa :( –

Odpowiedz

154

Ta odpowiedź jest zestawione z Loading large bitmaps Efficiently który wyjaśnia, jak korzystać inSampleSize załadować dół skalowane bitmapy wersję.

W szczególności Pre-scaling bitmaps wyjaśnia szczegóły różnych metod , jak je łączyć i które są najbardziej wydajne pod względem pamięci.

Istnieją trzy dominujące sposoby zmiany rozmiaru mapy bitowej na Androida, które mają różne właściwości pamięci:

createScaledBitmap API

Ten interfejs API weźmie w istniejącym bitmapy i utworzyć nową bitmapę z dokładne wymiary, które wybrałeś.

Po stronie plusów możesz uzyskać dokładnie taki rozmiar obrazu, jakiego szukasz (niezależnie od tego, jak wygląda). Ale wadą, jest to, że ten interfejs API wymaga istniejącej mapy bitowej , aby działać. Oznacza to, że obraz musi zostać wczytany, zdekodowany i utworzona mapa bitowa, zanim będzie można utworzyć nową, mniejszą wersję. Jest to idealne rozwiązanie pod względem uzyskania dokładnych wymiarów, ale straszne pod względem dodatkowego obciążenia pamięci. Jako taka, jest to rodzaj-of brakło dla większości twórców aplikacji, którzy wydają się być pamięć świadomej

inSampleSize flag

BitmapFactory.Options ma właściwość zauważyć jak inSampleSize że będzie zmienić rozmiar obrazu podczas dekodowania, w celu uniknięcia potrzeba dekodowania do tymczasowej bitmapy. Ta użyta tutaj wartość całkowita spowoduje załadowanie obrazu o zmniejszonej wielkości 1/x. Na przykład ustawienie wartości inSampleSize na 2 powoduje zwrócenie obrazu o połowę mniejszego, a ustawienie wartości 4 powoduje zwrócenie obrazu o wielkości 1/4 rozmiaru. Zasadniczo rozmiary obrazków zawsze będą miały trochę mocy mniejszej niż rozmiar źródła.

Z perspektywy pamięci korzystanie z inSampleSize jest naprawdę szybką operacją. Skutecznie, tylko dekoduje każdy X-piksel obrazu w wynikowej bitmapie. Są dwa główne problemy z inSampleSize jednak:

  • To nie daje dokładnych uchwał. Zmniejsza tylko rozmiar mapy bitowej o wartość 2.

  • Nie zapewnia najlepszej jakości zmiana rozmiaru. Większość filtrów zmienia rozmiar, tworząc dobrze wyglądające obrazy, czytając bloki pikseli, a następnie ważąc je, aby uzyskać odpowiedni rozmiar piksela.inSampleSize omija to wszystko, czytając co kilka pikseli. Rezultat jest dość wydajny i ma niską pamięć, ale cierpi na jakość.

Jeśli masz do czynienia tylko z kurczy swój wizerunek przez jakiegoś rozmiaru pow2 i filtrowanie nie jest problemem, to nie można znaleźć bardziej wydajne pamięci (lub wydajność wydajny) metoda niż inSampleSize.

inScaled, inDensity, inTargetDensity flags

Jeśli chcesz przeskalować obraz do wymiaru, która nie jest równa sile dwóch, potem musisz się inScaled, inDensity i inTargetDensity flagi BitmapOptions. Po ustawieniu flagi inScaled system wyprowadzi wartość skalowania, aby zastosować ją do mapy bitowej, dzieląc wartość inTargetDensity według wartości inDensity.

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); 

Korzystając z tej metody będzie re-size obraz, a także zastosować „filtr zmiany rozmiaru” do niego, to znaczy, że efekt końcowy będzie wyglądać lepiej, bo jakiś dodatkowy matematyka została uwzględniona na etapie zmiana rozmiaru . Ale ostrzegamy: , że dodatkowy etap filtrowania zajmuje dodatkowy czas przetwarzania i można szybko dodać do dużych obrazów, co powoduje powolne zmiany rozmiaru i dodatkowe alokacje pamięci dla samego filtra.

Zasadniczo nie jest dobrym pomysłem zastosowanie tej techniki do obrazu, który jest znacznie większy niż żądany rozmiar, ze względu na dodatkowe obciążenie filtrujące.

magiczne połączenie

Z pamięcią i perspektywy wydajności, można połączyć te opcje dla najlepszych rezultatów. (Ustawienie inSampleSize, inScaled, inDensity i inTargetDensity flagi)

inSampleSize zostaną najpierw zastosowane do obrazu, dostając się do następnego power-of-dwa większe niż wielkości docelowej. Następnie, inDensity & inTargetDensity służą do skalowania wyniku do żądanych wymiarów, stosując operację filtra do czyszczenia obrazu.

Połączenie tych dwóch elementów jest o wiele szybszym działaniem, ponieważ krok inSampleSize spowoduje zmniejszenie liczby pikseli, które będą wymagane w kroku opartym na wartości Gęstość, aby 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 konieczności dopasowania obrazu do konkretnych wymiarów, i jakiś ładniejszy filtrowania, a następnie ta technika jest najlepszy most do uzyskania odpowiedniej wielkości, ale odbywa się w szybki, Ślad niskiej pamięci operacja.

Getting wymiary obrazu

Uzyskiwanie rozmiar obrazu bez dekodowania cały obraz Aby zmienić rozmiar bitmapy, trzeba znać przychodzące wymiary. Możesz użyć flagi inJustDecodeBounds, aby 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 zdekodować pierwszy rozmiar, a następnie obliczyć odpowiednie wartości do skalowania do rozdzielczości docelowego.

+1

byłoby świetnie, gdybyś mógł powiedzieć nam, czym jest dstWidth? – k0sh

+0

@ k0sh dstWIdth to szerokość ImageView dla gdzie jego przejście do "docelowej szerokości" lub dstWidth za krótkie – tyczj

+0

@tyczj dziękuję za odpowiedź, wiem co to jest, ale niektórzy mogą tego nie wiedzieć i skoro Colt, który rzeczywiście odpowiedział na to pytanie, być może mógłby to wyjaśnić ludzie nie dezorientują się. – k0sh

11

Miło (i dokładne), ponieważ ta odpowiedź jest bardzo skomplikowana. Zamiast ponownie wynajdować koło, należy rozważyć biblioteki takie jak Glide, Picasso, UIL, Ion lub dowolną liczbę innych, które implementują tę skomplikowaną i podatną na błędy logikę.

Colt sam zaleca nawet rzucić okiem na Glide i Picasso w Pre-scaling Bitmaps Performance Patterns Video.

Korzystając z bibliotek, można uzyskać wszystkie korzyści wymienione w odpowiedzi Colta, ale dzięki znacznie prostszym interfejsom API, które działają konsekwentnie w każdej wersji Androida.

Powiązane problemy