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

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

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

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

Ответ 1

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

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

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

createScaledBitmap API

Этот API примет существующий растровый рисунок и создаст NEW bitmap с выбранными вами точными размерами.

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

флаг inSampleSize

BitmapFactory.Options имеет свойство, отмеченное как inSampleSize, которое изменит размер вашего изображения во время его декодирования, чтобы избежать необходимости декодирования временного растрового изображения. Это целочисленное значение, используемое здесь, будет загружать изображение с уменьшенным размером 1/x. Например, установка inSampleSize to 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 сначала будет применен к изображению, доведя его до следующего уровня мощности до 2-х БОЛЬШИХ, чем ваш целевой размер. Затем 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

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

Ответ 2

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

Сам Колт даже рекомендует взглянуть на Glide и Picasso в Предварительный просмотр видеороликов с растровыми изображениями.

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