BitmapFactory.decodeResource возвращает изменяемое растровое изображение в Android 2.2 и неизменное растровое изображение в Android 1.6
Я занимаюсь разработкой приложения и тестирую его на своем устройстве под управлением Android 2.2. В моем коде я использую растровое изображение, полученное с помощью BitmapFactory.decodeResource, и я могу вносить изменения, вызывая bitmap.setPixels()
в теме. Когда я проверяю это на устройстве друга под управлением Android 1.6, я получаю IllegalStateException
в призыве к bitmap.setPixels
, Документация онлайн говорит IllegalStateException
выбрасывается из этого метода, когда растровое изображение является неизменным. Документация ничего не говорит о decodeResource
возвращение неизменного растрового изображения, но ясно, что это должно быть так.
Есть ли другой вызов, который я могу сделать, чтобы надежно получить изменяемое растровое изображение из ресурса приложения без необходимости второго Bitmap
объект (я мог бы создать изменяемый файл того же размера и нарисовать его в Canvas, но для этого потребовалось бы два растровых изображения одинакового размера, которые бы занимали вдвое больше памяти, чем я планировал)?
7 ответов
Вы можете преобразовать свое неизменное растровое изображение в изменяемое растровое изображение.
Я нашел приемлемое решение, которое использует только память одного растрового изображения.
Исходное растровое изображение сохраняется в исходном виде (RandomAccessFile) на диске (без оперативной памяти), затем исходное растровое изображение освобождается (теперь нет растрового изображения в памяти), и после этого информация о файле загружается в другое растровое изображение. Таким способом можно сделать точечную копию, хранящую только одну растровую память в оперативной памяти за раз.
Смотрите полное решение и реализацию здесь: Android: конвертируйте неизменяемое растровое изображение в изменяемое
Я добавил улучшение в это решение, которое теперь работает с любыми типами растровых изображений (ARGB_8888, RGB_565 и т. Д.) И удаляет временный файл. Смотрите мой метод:
/**
* Converts a immutable bitmap to a mutable bitmap. This operation doesn't allocates
* more memory that there is already allocated.
*
* @param imgIn - Source image. It will be released, and should not be used more
* @return a copy of imgIn, but muttable.
*/
public static Bitmap convertToMutable(Bitmap imgIn) {
try {
//this is the file going to use temporally to save the bytes.
// This file will not be a image, it will store the raw image data.
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "temp.tmp");
//Open an RandomAccessFile
//Make sure you have added uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
//into AndroidManifest.xml file
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
// get the width and height of the source bitmap.
int width = imgIn.getWidth();
int height = imgIn.getHeight();
Config type = imgIn.getConfig();
//Copy the byte to the file
//Assume source bitmap loaded using options.inPreferredConfig = Config.ARGB_8888;
FileChannel channel = randomAccessFile.getChannel();
MappedByteBuffer map = channel.map(MapMode.READ_WRITE, 0, imgIn.getRowBytes()*height);
imgIn.copyPixelsToBuffer(map);
//recycle the source bitmap, this will be no longer used.
imgIn.recycle();
System.gc();// try to force the bytes from the imgIn to be released
//Create a new bitmap to load the bitmap again. Probably the memory will be available.
imgIn = Bitmap.createBitmap(width, height, type);
map.position(0);
//load it back from temporary
imgIn.copyPixelsFromBuffer(map);
//close the temporary file and channel , then delete that also
channel.close();
randomAccessFile.close();
// delete the temp file
file.delete();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return imgIn;
}
Скопируйте растровое изображение на себя с изменяемой опцией true. Таким образом, не требуется ни дополнительного потребления памяти, ни длинных строк кода.
Bitmap bitmap= BitmapFactory.decodeResource(....);
bitmap= bitmap.copy(Bitmap.Config.ARGB_8888, true);
Сначала мы можем установить параметры для BitmapFactory путем создания экземпляра класса BitmapFactory.Options, а затем установить для поля параметров с именем "inMutable" значение true, а затем передать этот экземпляр параметров в decodeResource.
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inMutable = true;
Bitmap bp = BitmapFactory.decodeResource(getResources(), R.raw.white, opt);
Вот решение, которое я создал, которое использует внутреннее хранилище и не требует каких-либо новых разрешений, основываясь на идее "Derzu" и том факте, что начиная с сотовой структуры, это встроено:
/**decodes a bitmap from a resource id. returns a mutable bitmap no matter what is the API level.<br/>
might use the internal storage in some cases, creating temporary file that will be deleted as soon as it isn't finished*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static Bitmap decodeMutableBitmapFromResourceId(final Context context, final int bitmapResId) {
final Options bitmapOptions = new Options();
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB)
bitmapOptions.inMutable = true;
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), bitmapResId, bitmapOptions);
if (!bitmap.isMutable())
bitmap = convertToMutable(context, bitmap);
return bitmap;
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public static Bitmap convertToMutable(final Context context, final Bitmap imgIn) {
final int width = imgIn.getWidth(), height = imgIn.getHeight();
final Config type = imgIn.getConfig();
File outputFile = null;
final File outputDir = context.getCacheDir();
try {
outputFile = File.createTempFile(Long.toString(System.currentTimeMillis()), null, outputDir);
outputFile.deleteOnExit();
final RandomAccessFile randomAccessFile = new RandomAccessFile(outputFile, "rw");
final FileChannel channel = randomAccessFile.getChannel();
final MappedByteBuffer map = channel.map(MapMode.READ_WRITE, 0, imgIn.getRowBytes() * height);
imgIn.copyPixelsToBuffer(map);
imgIn.recycle();
final Bitmap result = Bitmap.createBitmap(width, height, type);
map.position(0);
result.copyPixelsFromBuffer(map);
channel.close();
randomAccessFile.close();
outputFile.delete();
return result;
} catch (final Exception e) {
} finally {
if (outputFile != null)
outputFile.delete();
}
return null;
}
другой альтернативой является использование JNI для помещения в него данных, переработки исходного растрового изображения и использования данных JNI для создания нового растрового изображения, которое будет (автоматически) изменяемым, поэтому вместе с моим решением JNI для растровых изображений можно сделайте следующее:
Bitmap bitmap=BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher);
final JniBitmapHolder bitmapHolder=new JniBitmapHolder(bitmap);
bitmap.recycle();
bitmap=bitmapHolder.getBitmapAndFree();
Log.d("DEBUG",""+bitmap.isMutable()); //will return true
Однако я не уверен, каково минимальное требование уровня API. это работает очень хорошо на API 8 и выше.
Я знаю, что опаздываю на вечеринку, но именно так мы избежали этой мучительной проблемы с Android, обрезали и изменили изображение, сохранив только одну копию в памяти.
ситуация
мы хотим обработать пиксели обрезанной версии изображения, сохраненного в файл. С высокими требованиями к памяти, мы никогда не хотим иметь больше чем одну копию этого изображения в памяти в любой момент времени.
Что должно было сработать, но не сработало
Открытие подраздела изображения (бит, который мы хотели обрезать) с помощью BitmapRegionDecoder
проходя в BitmapFactory.option
с inMutable = true
, обработка пикселей, а затем сохранение в файл.
Хотя наше приложение объявило API минимум 14 и цель 19, BitmapRegionDecoder
возвращал неизменное растровое изображение, фактически игнорируя наши BitMapFactory.options
Что не сработает
- открывая изменяемое изображение с
BitmapFactory
(что уважает нашinMutable
вариант) и обрезка его: все методы обрезки не являются непроницаемыми (требуется, чтобы копия всего изображения существовала в памяти за раз, даже если мусор собирался сразу после перезаписи и переработки) - открывая неизменное изображение с
BitmapRegionDecoder
(эффективно обрезается) и преобразовывает его в изменчивый; все доступные методы снова требуют копии в памяти.
Отличный обход 2014
- открыть полноразмерное изображение с
BitmapFactory
как изменяемое растровое изображение, и выполнять наши операции с пикселями - сохранить растровое изображение в файл и переработать его из памяти (он все еще не обрезан)
- открыть сохраненный растр
BitmapRegionDecoder
, открывая только область для обрезки (теперь нам все равно, является ли растровое изображение неизменным или нет) - сохранить это растровое изображение (которое было эффективно обрезано) в файл, перезаписав ранее сохраненное растровое изображение (которое было обрезано)
С помощью этого метода мы можем обрезать и выполнять пиксельную обработку на растровом изображении, храня в памяти только 1 копию (поэтому мы можем избежать этих надоедливых ошибок OOM, насколько это возможно), торгуя оперативной памятью за время, поскольку мы должны выполнить дополнительный (медленный) файл ИО.
Это происходит потому, что вы хотите изменить размер растрового изображения, вызвав
setHeight()
или же
setWidth()
Чтобы изменить размер любого растрового или рисованного изображения (Png, Svg, вектор и т. Д.)
public Bitmap editMyBitmap(int drawableId, int newHeight, int newWidth) {
Bitmap myBitmap = BitmapFactory.decodeResource(getResources(), drawableId);
myBitmap = Bitmap.createScaledBitmap(myBitmap, newWidth, newHeight, false);
return myBitmap;
}
Пример использования:
Bitmap facebookIcon = editMyBitmap(R.drawable.facebookImage);
// now use it anywhere
imageView.setBitmapImage(facebookIcon);
canvas.drawBitmap(facebookIcon, 0, 0, null);
Я знаю, что вопрос решен, но как насчет:
BitmapFactory.decodeStream(getResources().openRawResource(getResources().getIdentifier("bitmapname", "drawable", context.getPackageName())))