BitmapFactory.decodeResource возвращает изменяемое растровое изображение в Android 2.2 и неизменяемое растровое изображение в Android 1.6
Я разрабатываю приложение и тестирую его на своем устройстве под управлением Android 2.2. В моем коде я использую растровое изображение, которое я извлекаю с помощью BitmapFactory.decodeResource , и я могу вносить изменения, вызывая bitmap.setPixels()
в нем. Когда я тестирую это на устройстве друга под управлением Android 1.6, я получаю IllegalStateException
в вызове bitmap.setPixels
. В онлайн-документации говорится, что из этого метода выбрасываетсяIllegalStateException
, когда растровое изображение является неизменяемым. В документации ничего не говорится о decodeResource
возврате неизменяемого растрового изображения, но очевидно, что так и должно быть.
Есть ли другой вызов, который я могу выполнить, чтобы надежно получить изменяемое растровое изображение из ресурса приложения без использования второго Bitmap
объекта (я мог бы создать изменяемое растровое изображение того же размера и нарисовать его на холсте, обернув его, но для этого потребовалось бы два растровых изображения одинакового размера, используя в два раза больше памяти, чем я предполагал)?
Переведено автоматически
Ответ 1
Вы можете преобразовать свое неизменяемое растровое изображение в изменяемое растровое изображение.
Я нашел приемлемое решение, которое использует память только одного растрового изображения.
Исходное растровое изображение сохраняется в необработанном виде (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;
}
Ответ 2
Скопируйте растровое изображение в себя с изменяемой опцией true . Таким образом, не требуется ни дополнительного потребления памяти, ни длинных строк кода.
Bitmap bitmap= BitmapFactory.decodeResource(....);
bitmap= bitmap.copy(Bitmap.Config.ARGB_8888, true);
Ответ 3
Сначала мы можем задать параметры для BitmapFactory, создав экземпляр BitmapFactory.Класс Options, а затем установите для поля options с именем 'inMutable' значение true, а затем передайте этот экземпляр options в decodeResource.
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inMutable = true;
Bitmap bp = BitmapFactory.decodeResource(getResources(), R.raw.white, opt);
Ответ 4
Вот созданное мной решение, которое использует внутреннее хранилище и не требует никаких новых разрешений, основанное на идее "Derzu" и том факте, что, начиная с honeycomb, это встроено в :
/**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 и выше.