Самый эффективный способ изменения размера растровых изображений на Android?

115

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

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

Какой способ изменения размера растровых изображений на Android является наиболее эффективным с точки зрения памяти?

Кольт МакАнлис
источник
7
Может ли ваш сервер отправить неправильный размер, чтобы вы сэкономили оперативную память и пропускную способность вашего клиента !?
Джеймс
2
Это справедливо только в том случае, если я владел серверным ресурсом, с ним был доступен вычислительный компонент, и во всех случаях он мог предсказать точные размеры изображений для соотношений сторон, которых он еще не видел. Так что, если вы загружаете контент ресурса из стороннего CDN (например, я), он не работает :(
Colt McAnlis

Ответы:

168

Этот ответ резюмируется в статье « Эффективная загрузка больших растровых изображений», в которой объясняется, как использовать inSampleSize для загрузки уменьшенной версии растрового изображения.

В частности, растровые изображения с предварительным масштабированием объясняют детали различных методов, способы их комбинирования и наиболее эффективные с точки зрения памяти.

Существует три основных способа изменения размера растрового изображения на Android, которые имеют разные свойства памяти:

createScaledBitmap API

Этот API примет существующее растровое изображение и создаст НОВОЕ растровое изображение с точными размерами, которые вы выбрали.

С другой стороны, вы можете получить изображение именно того размера, который вам нужен (независимо от того, как оно выглядит). Но недостатком является то , что этому API для работы требуется существующее растровое изображение . Это означает, что изображение должно быть загружено, декодировано и создано растровое изображение, прежде чем можно будет создать новую, меньшую версию. Это идеально с точки зрения получения точных размеров, но ужасно с точки зрения дополнительных накладных расходов на память. Таким образом, это своего рода нарушение условий для большинства разработчиков приложений, которые склонны уделять внимание памяти.

флаг inSampleSize

BitmapFactory.Optionsимеет свойство, отмеченное как inSampleSizeизменение размера изображения при его декодировании, чтобы избежать необходимости декодировать во временное растровое изображение. Это целое число, используемое здесь, загрузит изображение с уменьшенным размером в 1 / x. Например, установка значения inSampleSize2 возвращает изображение, размер которого вдвое меньше, а установка значения 4 возвращает изображение, размер которого составляет 1/4. Обычно размеры изображений всегда будут в несколько раз меньше размера исходного изображения.

С точки зрения памяти использование inSampleSize- действительно быстрая операция. Фактически, он будет декодировать только каждый X-й пиксель вашего изображения в полученное растровое изображение. Однако есть две основные проблемы inSampleSize:

  • Он не дает вам точного разрешения . Он только уменьшает размер вашего растрового изображения на некоторую степень 2.

  • Изменение размера не самого лучшего качества . Большинство фильтров изменения размера создают красивые изображения, считывая блоки пикселей и затем взвешивая их, чтобы получить соответствующий пиксель с измененным размером. inSampleSizeпозволяет избежать всего этого, просто считывая каждые несколько пикселей. Результат довольно производительный, мало памяти, но страдает качество.

Если вы имеете дело только с уменьшением изображения на некоторый размер pow2, и фильтрация не является проблемой, то вы не можете найти более эффективный с точки зрения памяти (или производительный) метод, чем inSampleSize.

флаги inScaled, inDensity, inTargetDensity

Если вам нужно масштабировать изображение до размера, не равного степени двойки, вам понадобятся флаги inScaled, inDensityи . Когда флаг установлен, система вычислит значение масштабирования для применения к растровому изображению путем деления на значения.inTargetDensityBitmapOptionsinScaledinTargetDensityinDensity

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
Я смотрел серию шаблонов производительности Android и многому научился!
Anis
13

Каким бы красивым (и точным) ни был этот ответ, он также очень сложен. Вместо того, чтобы заново изобретать колесо, подумайте о таких библиотеках, как Glide , Picasso , UIL , Ion или о любых других, которые реализуют эту сложную и подверженную ошибкам логику.

Сам Кольт даже рекомендует взглянуть на Glide и Picasso в видеоролике Pre-scaling Bitmaps Performance Patterns .

Используя библиотеки, вы можете получить каждый бит эффективности, упомянутый в ответе Кольта, но с гораздо более простыми API-интерфейсами, которые работают согласованно во всех версиях Android.

Сэм Джадд
источник