Самий ефективний спосіб пам'яті змінити розмір растрових файлів на Android?


115

Я будую соціальний додаток, що займає зображення, де зображення надсилаються з сервера на пристрій. Коли пристрій має меншу роздільну здатність екрана, мені потрібно змінити розмір растрових зображень на пристрої, щоб відповідати їх передбачуваним розмірам дисплея.

Проблема полягає в тому, що використання createScaledBitmap змушує мене зіткнутися з безліччю помилок із пам'яттю після зміни розміру орди мініатюрних зображень.

Що є найбільш ефективним способом зміни розміру растрових зображень на Android?


7
Чи може ваш сервер не надіслати потрібний розмір, щоб ви зберегли оперативну пам’ять і пропускну здатність клієнта !?
Джеймс

2
Це справедливо лише в тому випадку, якщо я володів ресурсом сервера, у ньому був доступний обчислювальний компонент, і у всіх випадках він міг передбачити точні розміри зображень для співвідношення сторін, яких він ще не бачив. Отже, якщо ви завантажуєте вміст ресурсів із стороннього CDN (як і я), він не працює :(
Кольт МакАнліс

Відповіді:


168

Ця відповідь підсумована із завантаження великих растрових зображень. Ефективно пояснює, як використовувати inSampleSize для завантаження зменшеної версії растрових зображень.

Зокрема, растрові зображення попереднього масштабування пояснюють деталі різних методів, способи їх поєднання та які є найбільш ефективними в пам'яті.

Існує три домінуючі способи змінити розмір растрової карти на Android, які мають різні властивості пам'яті:

createScaledBitmap API

Цей API візьме наявну растрову карту та створить НОВУ растрову карту із точними вибраними розмірами.

З іншого боку, ви можете отримати саме той розмір зображення, який ви шукаєте (незалежно від того, як воно виглядає). Мінусом є те, що для роботи цього API потрібна наявна растрова карта . Це означає, що зображення доведеться завантажувати, розшифровувати та створювати растрові карти, перш ніж мати можливість створити нову, меншу версію. Це ідеально з точки зору отримання точних розмірів, але жахливо з точки зору додаткових витрат на пам'ять. Таким чином, для більшості розробників додатків це вимикач укладення угод, який, як правило, має пам'ять

inSampleSize прапор

BitmapFactory.Optionsмає властивість, яка зазначає, inSampleSizeщо дозволить змінити розмір зображення під час його розшифровки, щоб уникнути необхідності декодування до тимчасової растрової карти. Це ціле значення, яке тут використовується, завантажить зображення у зменшеному розмірі на 1 / х. Наприклад, якщо встановити inSampleSizeзначення 2, повертає зображення, яке наполовину менше, а якщо встановити його на 4, повертає зображення, яке має розмір 1/4. В основному розміри зображень завжди будуть на дві потужності меншими за розмір джерела.

З точки зору пам'яті, використання inSampleSize- це дійсно швидка операція. Ефективно, він декодує лише кожен X-ій піксель вашого зображення в отриману растрову карту. Однак є дві основні проблеми inSampleSize:

  • Це не дає точних рішень . Це лише зменшує розмір вашої растрової карти на деяку потужність 2.

  • Він не дає найкращої якості розміру . Більшість фільтрів змінюють розмір, створюючи добре виглядаючі зображення, читаючи блоки пікселів, а потім зважують їх для отримання відповідного розміру пікселя. inSampleSizeуникає всього цього, просто читаючи кожні кілька пікселів. Результат є досить ефективним, і мало пам’яті, але якість страждає.

Якщо ви маєте справу лише зі зменшенням зображення на якийсь розмір pow2, а фільтрація не є проблемою, тоді ви не можете знайти більш ефективний (або ефективний) спосіб пам'яті, ніж метод inSampleSize.

inScaled, inDensity, inTargetDensity прапори

Якщо вам потрібно масштабувати зображення до розміру , яка не дорівнює ступеню двійки, то вам потрібен inScaled, inDensityі inTargetDensityпрапори BitmapOptions. Коли inScaledпрапор встановлений, система отримає значення масштабування, яке застосовується до вашої растрової карти, діливши значення inTargetDensityна 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);

Використання цього методу змінить розмір зображення, а також застосує до нього «фільтр зміни розміру», тобто кінцевий результат буде виглядати краще, оскільки деяка додаткова математика була врахована під час кроку зміни розміру. Але будьте попереджені: цей додатковий крок фільтра вимагає додаткового часу на обробку і може швидко додавати великі зображення, що призводить до повільних розмірів та додаткових розподілів пам'яті для самого фільтра.

Зазвичай це не дуже гарна ідея застосовувати цю техніку до зображення, яке значно перевищує бажаний розмір, через додаткові фільтруючі накладні витрати.

Чарівне поєднання

З точки зору пам’яті та продуктивності, ви можете комбінувати ці параметри для найкращих результатів. (Встановивши inSampleSize, inScaled, inDensityі inTargetDensityпрапори)

inSampleSizeспочатку буде застосовано до зображення, отримавши його до наступної потужності на два ВЕЛИЧЕ, ніж ваш цільовий розмір. Потім inDensity& & inTargetDensityвикористовуються для масштабування результату до точних розмірів, які ви хочете, застосувавши операцію фільтра для очищення зображення.

Поєднання цих двох inSampleSizeфункцій - набагато швидша операція, оскільки цей крок зменшить кількість пікселів, до яких в результаті кроку, заснованого на щільності, потрібно застосувати фільтр зміни розміру.

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

Якщо вам потрібно пристосувати зображення до конкретних розмірів і приємніше фільтрувати, то ця методика є найкращим мостом до отримання потрібного розміру, але виконана в швидкій операції, що забезпечує низьку пам'ять.

Отримання розмірів зображення

Отримання розміру зображення без розшифровки всього зображення Щоб змінити розмір растрової карти, вам потрібно знати розміри вхідних зображень. Ви можете використовувати inJustDecodeBoundsпрапор, щоб допомогти вам отримати розміри зображення, без необхідності фактично декодувати дані пікселів.

// 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

Ви можете використовувати цей прапор, щоб спершу розшифрувати розмір, а потім обчислити належні значення масштабування до цільової роздільної здатності.


1
це було б чудово, якби ви могли сказати нам, що таке dstWidth?
k0sh

@ k0sh dstWIdth - ширина ImageView для того, куди йде його тобто, destination widthабо dstWidth для коротких
tyczj

@tyczj дякую за відповідь, я знаю, що це таке, але деякі можуть цього не знати, і оскільки Кольт насправді відповів на це питання, можливо, він міг би пояснити це, щоб люди не заплутувались.
k0sh

Приємний пост ... раніше не знав про прапорці InScaled, inDensity, InTargetDensity ...
maveroid

Я дивився серії моделей андроїд, і я багато чого навчився!
Аніс

13

Як приємна (і точна) ця відповідь, це також дуже складно. Замість того, щоб заново вигадувати колесо, розгляньте такі бібліотеки, як Glide , Picasso , UIL , Ion або будь-яка кількість інших, які реалізують цю складну для вас логіку, схильну до помилок.

Сам Кольт навіть рекомендує поглянути на Glide та Picasso у відеороликах попереднього масштабування біт-карти .

Використовуючи бібліотеки, ви можете отримати будь-яку ефективність, зазначену у відповіді Колта, але набагато простішими API, які стабільно працюють у кожній версії Android.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.