BitmapFactory.decodeResource возвращает измененный битмап в Android 2.2 и неизменный Bitmap в Android 1.6

Я разрабатываю приложение и тестирую его на своем устройстве под управлением Android 2.2. В моем коде я использую Bitmap, который я извлекаю с помощью BitmapFactory.decodeResource, и я могу вносить изменения, вызывая bitmap.setPixels() на нем. Когда я тестирую это на другом устройстве под управлением Android 1.6, я получаю IllegalStateException в вызове bitmap.setPixels. Документация онлайн говорит, что IllegalStateException выбрасывается из этого метода, когда растровое изображение неизменно. В документации ничего не говорится о decodeResource возвращении неизменяемого растрового изображения, но ясно, что это должно быть так.

Есть ли другой вызов, который я могу сделать для надежного получения измененного битового массива из ресурса приложения, не требуя второго объекта Bitmap (я мог бы создать изменяемый один размер и нарисовать его на холсте, но это требуют двух растровых изображений равного размера, используя в два раза больше памяти, чем я предполагал)?

Ответ 1

Вы можете преобразовать неизменяемое растровое изображение в изменяемое растровое изображение.

Я нашел приемлемое решение, которое использует только память одного растрового изображения.

Исходный битмап является сырым сохраненным (RandomAccessFile) на диске (нет памяти RAM), затем открывается растровое изображение источника (теперь нет растрового изображения в памяти), и после этого информация о файле загружается в другое растровое изображение. Этот способ позволяет сделать растровую копию, имеющую только одно растровое изображение, хранящееся в памяти RAM за раз.

Посмотрите полное решение и реализацию здесь: Android: конвертировать неизменяемый Bitmap в Mutable

Я добавляю усовершенствование этого решения, которое теперь работает с любым типом растровых изображений (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

Вы правы, используя decodeResource, вы получите unmutable bitmap (чтобы проверить его, вы можете вызвать метод isMutable объекта Bitmap).

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

Bitmap immutableBitmap = BitmapFactory.decodeResource(....);
Bitmap mutableBitmap = immutableBitmap.copy(Bitmap.Config.ARGB_8888, true);

Я надеюсь, что эта информация будет полезна для вас.

Ответ 3

Мы можем сначала установить параметры для BitmapFactory, создав экземпляр класса BitmapFactory.Options, а затем установите поле опций с именем 'inMutable' как true и затем передаем этот экземпляр параметров для decodeResource.

 BitmapFactory.Options opt = new BitmapFactory.Options();
 opt.inMutable = true;
 Bitmap bp = BitmapFactory.decodeResource(getResources(), R.raw.white, opt);

Ответ 4

Здесь созданное решение, использующее внутреннее хранилище и не требующее какого-либо нового разрешения на основе идеи 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 и выше.

Ответ 5

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

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

Что должно было сработать, но не
Открыв подразделение изображения (бит, который мы хотели обрезать) с помощью BitmapRegionDecoder, передав BitmapFactory.option с inMutable = true, обработав пиксели, а затем сохраним их в файл.
Хотя наше приложение объявило API минимум 14 и цель 19, BitmapRegionDecoder возвращала неизменяемое растровое изображение, фактически игнорируя наш BitMapFactory.options

Что не будет работать

  • открытие измененного изображения с помощью BitmapFactory (что соответствует нашей опцией inMutable) и обрезать его: все методы обрезки не являются императивными (требуется копирование всего изображения, которое будет существовать в памяти за раз, даже если мусор собранные сразу после перезаписывания и переработки)
  • открытие неизменяемого изображения с BitmapRegionDecoder (эффективно обрезано) и преобразование его в изменчивое; все доступные методы снова требуют копии в памяти.

Отличная работа 2014 года

  • откройте полноразмерное изображение с помощью BitmapFactory в качестве изменяемого растрового изображения и выполните операции с пикселями
  • сохранить растровое изображение в файл и переработать его из памяти (он все еще не обрезается)
  • откройте сохраненное растровое изображение с помощью BitmapRegionDecoder, открыв только область, подлежащую обрезанию (теперь нам все равно, является ли растровое изображение неизменным или нет)
  • сохраните это растровое изображение (которое было эффективно обрезано) в файл, перезаписав ранее сохраненное растровое изображение (которое не было обрезано)

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

Ответ 6

Я знаю, что вопрос решен, но как насчет:

BitmapFactory.decodeStream(getResources().openRawResource(getResources().getIdentifier("bitmapname", "drawable", context.getPackageName())))