Изменение размера большого растрового файла для масштабирования выходного файла на Android

218

У меня есть большое растровое изображение (скажем, 3888x2592) в файле. Теперь я хочу изменить размер этого растрового изображения до 800x533 и сохранить его в другом файле. Обычно я бы масштабировать растровое изображение с помощью вызова Bitmap.createBitmapметода , но он нуждается в исходный битовый массив в качестве первого аргумента, который не может обеспечить загрузку , потому что исходное изображение в объект Bitmap бы, конечно , превышает память (см здесь , например).

Я также не могу прочитать растровое изображение, например, с BitmapFactory.decodeFile(file, options)предоставлением BitmapFactory.Options.inSampleSize, потому что я хочу изменить его размер до точной ширины и высоты. Использование inSampleSizeизменило бы размер растрового изображения до 972x648 (если я использую inSampleSize=4) или до 778x518 (если я использую inSampleSize=5, что даже не равно степени 2).

Я также хотел бы избежать чтения изображения с использованием inSampleSize, например, с 972x648 на первом этапе, а затем с изменением его размера до 800x533 на втором этапе, поскольку качество будет низким по сравнению с прямым изменением размера исходного изображения.

Подводя итог моему вопросу: есть ли способ прочитать большой файл изображения с разрешением 10MP или более и сохранить его в новый файл изображения с изменением размера до определенной новой ширины и высоты, без получения исключения OutOfMemory?

Я также попытался BitmapFactory.decodeFile(file, options)установить значения параметров Options.outHeight и Options.outWidth вручную на 800 и 533, но это не сработало.

Manuel
источник
Нет, outHeight и outWidth являются параметрами out из метода декодирования. При этом у меня та же проблема, что и у вас, и я не очень доволен двухэтапным подходом.
Rds
часто, слава богу, вы можете использовать одну строку кода .. stackoverflow.com/a/17733530/294884
Fattie
Читатели, обратите внимание, это абсолютно критический QA !!! stackoverflow.com/a/24135522/294884
Толстяк
1
Просьба учесть, что этому вопросу уже 5 лет, и полное решение - это: stackoverflow.com/a/24135522/294884 Ура!
Толстяк
2
Существует в настоящее время официальная документация по этой теме: developer.android.com/training/displaying-bitmaps/...
Винс

Ответы:

146

Нет . Я бы хотел , чтобы кто - то исправить меня, но я принял груз / изменения размера подход вы пытались в качестве компромисса.

Вот шаги для любого просмотра:

  1. Рассчитайте максимально возможное, inSampleSizeчто все еще дает изображение больше, чем ваша цель.
  2. Загрузите изображение, используя BitmapFactory.decodeFile(file, options), передав inSampleSize в качестве опции.
  3. Измените размеры до желаемых размеров, используя Bitmap.createScaledBitmap().
Джастин
источник
Я пытался избежать этого. То есть нет способа напрямую изменить размер большого изображения всего за один шаг?
Мануэль
2
Насколько мне известно, но не позволяйте этому помешать вам исследовать это дальше.
Джастин
Хорошо, я приму это за мой принятый ответ до сих пор. Если я найду другие способы, я дам вам знать.
Мануэль
Как PSIXO упомянул в ответе, вы также можете использовать android: largeHeap, если у вас все еще остаются проблемы после использования inSampleSize.
user276648
переменная растрового изображения становилась пустой
Прасад
99

Джастин ответ переведен в код (отлично подходит для меня):

private Bitmap getBitmap(String path) {

Uri uri = getImageUri(path);
InputStream in = null;
try {
    final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
    in = mContentResolver.openInputStream(uri);

    // Decode image size
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, options);
    in.close();



    int scale = 1;
    while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) > 
          IMAGE_MAX_SIZE) {
       scale++;
    }
    Log.d(TAG, "scale = " + scale + ", orig-width: " + options.outWidth + ", 
       orig-height: " + options.outHeight);

    Bitmap resultBitmap = null;
    in = mContentResolver.openInputStream(uri);
    if (scale > 1) {
        scale--;
        // scale to max possible inSampleSize that still yields an image
        // larger than target
        options = new BitmapFactory.Options();
        options.inSampleSize = scale;
        resultBitmap = BitmapFactory.decodeStream(in, null, options);

        // resize to desired dimensions
        int height = resultBitmap.getHeight();
        int width = resultBitmap.getWidth();
        Log.d(TAG, "1th scale operation dimenions - width: " + width + ",
           height: " + height);

        double y = Math.sqrt(IMAGE_MAX_SIZE
                / (((double) width) / height));
        double x = (y / height) * width;

        Bitmap scaledBitmap = Bitmap.createScaledBitmap(resultBitmap, (int) x, 
           (int) y, true);
        resultBitmap.recycle();
        resultBitmap = scaledBitmap;

        System.gc();
    } else {
        resultBitmap = BitmapFactory.decodeStream(in);
    }
    in.close();

    Log.d(TAG, "bitmap size - width: " +resultBitmap.getWidth() + ", height: " + 
       resultBitmap.getHeight());
    return resultBitmap;
} catch (IOException e) {
    Log.e(TAG, e.getMessage(),e);
    return null;
}
Офир
источник
15
Трудно читать, когда вы используете переменные, такие как "b", но хороший ответ, тем не менее.
Оливер Диксон
@Ofir: getImageUri (путь); что я должен передать в этом методе?
Biginner
1
Вместо (w h) /Math.pow (scale, 2) более эффективно использовать (w h) >> scale.
david.perez
2
Не звоните, System.gc()пожалуйста
gw0
Спасибо @Ofir, но это преобразование не сохраняет ориентацию изображения: - /
JoeCoolman
43

Это «объединенные» решения Моджо Рисина и Офира. Это даст вам пропорционально измененное изображение с границами максимальной ширины и максимальной высоты.

  1. Он читает только метаданные, чтобы получить оригинальный размер (options.inJustDecodeBounds)
  2. Для экономии памяти используется приблизительное изменение размера (itmap.createScaledBitmap)
  3. Это использует точно измененное изображение, основанное на грубом Bitamp, созданном ранее.

Для меня это было хорошо на 5 мегапиксельных изображениях ниже.

try
{
    int inWidth = 0;
    int inHeight = 0;

    InputStream in = new FileInputStream(pathOfInputImage);

    // decode image size (decode metadata only, not the whole image)
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, options);
    in.close();
    in = null;

    // save width and height
    inWidth = options.outWidth;
    inHeight = options.outHeight;

    // decode full image pre-resized
    in = new FileInputStream(pathOfInputImage);
    options = new BitmapFactory.Options();
    // calc rought re-size (this is no exact resize)
    options.inSampleSize = Math.max(inWidth/dstWidth, inHeight/dstHeight);
    // decode full image
    Bitmap roughBitmap = BitmapFactory.decodeStream(in, null, options);

    // calc exact destination size
    Matrix m = new Matrix();
    RectF inRect = new RectF(0, 0, roughBitmap.getWidth(), roughBitmap.getHeight());
    RectF outRect = new RectF(0, 0, dstWidth, dstHeight);
    m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
    float[] values = new float[9];
    m.getValues(values);

    // resize bitmap
    Bitmap resizedBitmap = Bitmap.createScaledBitmap(roughBitmap, (int) (roughBitmap.getWidth() * values[0]), (int) (roughBitmap.getHeight() * values[4]), true);

    // save image
    try
    {
        FileOutputStream out = new FileOutputStream(pathOfOutputImage);
        resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
    }
    catch (Exception e)
    {
        Log.e("Image", e.getMessage(), e);
    }
}
catch (IOException e)
{
    Log.e("Image", e.getMessage(), e);
}
blubl
источник
23

Почему бы не использовать API?

int h = 48; // height in pixels
int w = 48; // width in pixels    
Bitmap scaled = Bitmap.createScaledBitmap(largeBitmap, w, h, true);
Bostone
источник
21
Потому что это не решило бы мою проблему. А именно: «... ему нужен исходный точечный рисунок в качестве первого аргумента, который я не могу предоставить, потому что загрузка исходного изображения в растровый объект, конечно, превысила бы память». Поэтому я не могу передать Bitmap методу .createScaledBitmap, потому что мне все равно нужно сначала загрузить большое изображение в объект Bitmap.
Мануэль
2
Правильно. Я перечитал ваш вопрос и в основном (если я правильно понял) он сводится к «могу ли я изменить размер изображения до точных размеров без загрузки исходного файла в память?» Если так - я не знаю достаточно о тонкостях обработки изображений, чтобы ответить на них, но что-то подсказывает мне, что 1. это не доступно из API, 2. оно не будет 1-строчным. Я отмечу это как любимое - было бы интересно посмотреть, решите ли вы (или кто-то еще) это.
Бостон
это сработало для меня, потому что я получаю URI и конвертирую в растровое изображение, так что масштабировать их легко для меня 1+ для самого простого.
Хамза
22

Принимая во внимание другой отличный ответ, лучший код, который я когда-либо видел для этого, находится в документации к инструменту для фотосъемки.

См. Раздел «Декодирование масштабированного изображения».

http://developer.android.com/training/camera/photobasics.html

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

Я скопировал приведенный ниже код как готовую функцию для удобства.

private void setPic(String imagePath, ImageView destination) {
    int targetW = destination.getWidth();
    int targetH = destination.getHeight();
    // Get the dimensions of the bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(imagePath, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Determine how much to scale down the image
    int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

    // Decode the image file into a Bitmap sized to fill the View
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(imagePath, bmOptions);
    destination.setImageBitmap(bitmap);
}
Alex
источник
1
Сначала вы делите целые числа на то, что будет в итоге. Во-вторых, код падает с targetW или targetH, равным 0 (хотя это не имеет особого смысла, я знаю). Третий inSampleSize должен быть степенью 2.
cybergen
Не пойми меня неправильно. Это определенно загрузит изображение, но если полы с отступами, это не выглядит так. И это также определенно не правильный ответ, потому что изображение не будет масштабироваться, как ожидалось. Он не будет ничего делать, пока размер изображения не станет в два раза меньше или меньше. Тогда ничего не происходит, пока размер изображения не станет 1/4 от размера изображения. И так далее с полномочиями двух!
cybergen
18

После прочтения этих ответов и документации Android приведен код для изменения размера растрового изображения без загрузки его в память:

public Bitmap getResizedBitmap(int targetW, int targetH,  String imagePath) {

    // Get the dimensions of the bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    //inJustDecodeBounds = true <-- will not load the bitmap into memory
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(imagePath, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Determine how much to scale down the image
    int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

    // Decode the image file into a Bitmap sized to fill the View
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(imagePath, bmOptions);
    return(bitmap);
}
penduDev
источник
Обратите внимание, что bmOptions.inPurgeable = true; устарела.
Равит
6

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

BitmapFactory.Options options = new BitmapFactory.Options();
InputStream is = null;
is = new FileInputStream(path_to_file);
BitmapFactory.decodeStream(is,null,options);
is.close();
is = new FileInputStream(path_to_file);
// here w and h are the desired width and height
options.inSampleSize = Math.max(options.outWidth/w, options.outHeight/h);
// bitmap is the resized bitmap
Bitmap bitmap = BitmapFactory.decodeStream(is,null,options);
Моджо Рисин
источник
1
Поскольку inSampleSize - это целое число, вы очень редко получаете точную ширину и высоту пикселя, которые хотите получить. Иногда вы можете приблизиться, но вы также можете быть далеко от него, в зависимости от десятичного знака.
Мануэль
Утро, я попробовал ваш код (пост выше в этой теме), но, кажется, не работает, где я сделал не так? Любые предложения приветствуются :-)
RRTW
5

Это может быть полезно для кого-то еще, смотрящего на этот вопрос. Я переписал код Джастина, чтобы метод также мог получить требуемый объект целевого размера. Это работает очень хорошо при использовании Canvas. Вся заслуга должна идти к Джастину за его отличный исходный код.

    private Bitmap getBitmap(int path, Canvas canvas) {

        Resources resource = null;
        try {
            final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
            resource = getResources();

            // Decode image size
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeResource(resource, path, options);

            int scale = 1;
            while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) > 
                  IMAGE_MAX_SIZE) {
               scale++;
            }
            Log.d("TAG", "scale = " + scale + ", orig-width: " + options.outWidth + ", orig-height: " + options.outHeight);

            Bitmap pic = null;
            if (scale > 1) {
                scale--;
                // scale to max possible inSampleSize that still yields an image
                // larger than target
                options = new BitmapFactory.Options();
                options.inSampleSize = scale;
                pic = BitmapFactory.decodeResource(resource, path, options);

                // resize to desired dimensions
                int height = canvas.getHeight();
                int width = canvas.getWidth();
                Log.d("TAG", "1th scale operation dimenions - width: " + width + ", height: " + height);

                double y = Math.sqrt(IMAGE_MAX_SIZE
                        / (((double) width) / height));
                double x = (y / height) * width;

                Bitmap scaledBitmap = Bitmap.createScaledBitmap(pic, (int) x, (int) y, true);
                pic.recycle();
                pic = scaledBitmap;

                System.gc();
            } else {
                pic = BitmapFactory.decodeResource(resource, path);
            }

            Log.d("TAG", "bitmap size - width: " +pic.getWidth() + ", height: " + pic.getHeight());
            return pic;
        } catch (Exception e) {
            Log.e("TAG", e.getMessage(),e);
            return null;
        }
    }

Код Джастина ОЧЕНЬ эффективен для сокращения накладных расходов при работе с большими растровыми изображениями.

Музыкальная Обезьяна
источник
4

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

Переменные imageUri, maxImageSideLengthи contextявляются параметрами моего метода. Для наглядности я разместил только реализацию метода без упаковки AsyncTask.

            ContentResolver resolver = context.getContentResolver();
            InputStream is;
            try {
                is = resolver.openInputStream(imageUri);
            } catch (FileNotFoundException e) {
                Log.e(TAG, "Image not found.", e);
                return null;
            }
            Options opts = new Options();
            opts.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(is, null, opts);

            // scale the image
            float maxSideLength = maxImageSideLength;
            float scaleFactor = Math.min(maxSideLength / opts.outWidth, maxSideLength / opts.outHeight);
            // do not upscale!
            if (scaleFactor < 1) {
                opts.inDensity = 10000;
                opts.inTargetDensity = (int) ((float) opts.inDensity * scaleFactor);
            }
            opts.inJustDecodeBounds = false;

            try {
                is.close();
            } catch (IOException e) {
                // ignore
            }
            try {
                is = resolver.openInputStream(imageUri);
            } catch (FileNotFoundException e) {
                Log.e(TAG, "Image not found.", e);
                return null;
            }
            Bitmap bitmap = BitmapFactory.decodeStream(is, null, opts);
            try {
                is.close();
            } catch (IOException e) {
                // ignore
            }

            return bitmap;
cybergen
источник
2
Очень хорошо! Использование inDensity вместо Bitmap.createScaledBitmap сэкономило мне кучу памяти. Еще лучше в сочетании с inSamplesize.
Остконтентан
2

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

  1. Узнайте размер измененного изображения с помощью вызова BitmapFactory.decodeFile и предоставления checkSizeOptions.inJustDecodeBounds
  2. Рассчитать максимум возможное значение inSampleSize, которое вы можете использовать на своем устройстве, чтобы не превышать объем памяти. bitmapSizeInBytes = 2 * ширина * высота; Как правило, для вашей картинки inSampleSize = 2 вполне подойдет, так как вам понадобится только 2 * 1944x1296) = 4.8Mbб, которые должны находиться в памяти.
  3. Используйте BitmapFactory.decodeFile с inSampleSize, чтобы загрузить растровое изображение
  4. Масштабируйте растровое изображение до точного размера.

Мотивация: многошаговое масштабирование может обеспечить более высокое качество изображения, однако нет гарантии, что оно будет работать лучше, чем использование высокого inSampleSize. На самом деле, я думаю, что вы также можете использовать inSampleSize как 5 (не pow из 2), чтобы иметь прямое масштабирование в одной операции. Или просто используйте 4, а затем вы можете просто использовать это изображение в пользовательском интерфейсе. если вы отправляете его на сервер - вы можете выполнить масштабирование до точного размера на стороне сервера, что позволит вам использовать расширенные методы масштабирования.

Примечания: если растровое изображение, загруженное на шаге 3, по крайней мере, в 4 раза больше (так что 4 * targetWidth <width), вы, вероятно, можете использовать несколько изменяющих размеры для достижения лучшего качества. по крайней мере, это работает в общем Java, в Android у вас нет возможности указать интерполяцию, используемую для масштабирования http://today.java.net/pub/a/today/2007/04/03/perils-of- изображения getscaledinstance.html

Андрей Чорный
источник
2

Я использовал такой код:

  String filePath=Environment.getExternalStorageDirectory()+"/test_image.jpg";
  BitmapFactory.Options options=new BitmapFactory.Options();
  InputStream is=new FileInputStream(filePath);
  BitmapFactory.decodeStream(is, null, options);
  is.close();
  is=new FileInputStream(filePath);
  // here w and h are the desired width and height
  options.inSampleSize=Math.max(options.outWidth/460, options.outHeight/288); //Max 460 x 288 is my desired...
  // bmp is the resized bitmap
  Bitmap bmp=BitmapFactory.decodeStream(is, null, options);
  is.close();
  Log.d(Constants.TAG, "Scaled bitmap bytes, "+bmp.getRowBytes()+", width:"+bmp.getWidth()+", height:"+bmp.getHeight());

Я пробовал исходное изображение 1230 x 1230, и полученное растровое изображение говорит о 330 x 330.
И если я попробовал 2590 x 3849, я получу OutOfMemoryError.

Я проследил это, он все еще выбрасывает OutOfMemoryError в строку "BitmapFactory.decodeStream (is, null, options);", если исходное растровое изображение слишком большое ...

RRTW
источник
2

Выше код сделал немного чище. InputStreams наконец-то закрыли обертку, чтобы убедиться, что они также закрыты:

* Примечание
Ввод: InputStream is, int w, int h
Вывод: растровое изображение

    try
    {

        final int inWidth;
        final int inHeight;

        final File tempFile = new File(temp, System.currentTimeMillis() + is.toString() + ".temp");

        {

            final FileOutputStream tempOut = new FileOutputStream(tempFile);

            StreamUtil.copyTo(is, tempOut);

            tempOut.close();

        }



        {

            final InputStream in = new FileInputStream(tempFile);
            final BitmapFactory.Options options = new BitmapFactory.Options();

            try {

                // decode image size (decode metadata only, not the whole image)
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeStream(in, null, options);

            }
            finally {
                in.close();
            }

            // save width and height
            inWidth = options.outWidth;
            inHeight = options.outHeight;

        }

        final Bitmap roughBitmap;

        {

            // decode full image pre-resized
            final InputStream in = new FileInputStream(tempFile);

            try {

                final BitmapFactory.Options options = new BitmapFactory.Options();
                // calc rought re-size (this is no exact resize)
                options.inSampleSize = Math.max(inWidth/w, inHeight/h);
                // decode full image
                roughBitmap = BitmapFactory.decodeStream(in, null, options);

            }
            finally {
                in.close();
            }

            tempFile.delete();

        }

        float[] values = new float[9];

        {

            // calc exact destination size
            Matrix m = new Matrix();
            RectF inRect = new RectF(0, 0, roughBitmap.getWidth(), roughBitmap.getHeight());
            RectF outRect = new RectF(0, 0, w, h);
            m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
            m.getValues(values);

        }

        // resize bitmap
        final Bitmap resizedBitmap = Bitmap.createScaledBitmap(roughBitmap, (int) (roughBitmap.getWidth() * values[0]), (int) (roughBitmap.getHeight() * values[4]), true);

        return resizedBitmap;

    }
    catch (IOException e) {

        logger.error("Error:" , e);
        throw new ResourceException("could not create bitmap");

    }
user1327738
источник
1

Чтобы масштабировать изображение «правильным» способом, не пропуская ни одного пикселя, вам нужно подключиться к декодеру изображений, чтобы выполнить пост-дискретизацию построчно. Android (и лежащая в его основе библиотека Skia) не предоставляет таких хуков, поэтому вам придется свернуть свои собственные. Предполагая, что вы говорите с изображениями в формате jpeg, лучше всего было бы напрямую использовать libjpeg в C.

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

D0SBoots
источник
1

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

http://bricolsoftconsulting.com/2012/12/07/handling-large-images-on-android/

Тео
источник
1

Если вы абсолютно хотите сделать один шаг изменения размера, вы можете загрузить весь растровый файл, если android: largeHeap = true, но, как вы можете видеть, это не очень рекомендуется.

Из документации: android: largeHeap Должны ли процессы вашего приложения создаваться с большой кучей Dalvik. Это относится ко всем процессам, созданным для приложения. Это относится только к первому приложению, загруженному в процесс; если вы используете общий идентификатор пользователя, чтобы разрешить нескольким приложениям использовать процесс, все они должны использовать эту опцию последовательно, иначе они будут иметь непредсказуемые результаты. Большинству приложений это не нужно, и вместо этого следует сосредоточиться на снижении общего использования памяти для повышения производительности. Включение этого также не гарантирует фиксированного увеличения доступной памяти, потому что некоторые устройства ограничены их общей доступной памятью.

Игорь Чордаш
источник
0

Это сработало для меня. Функция получает путь к файлу на SD-карте и возвращает растровое изображение в максимальном отображаемом размере. Код от Ofir с некоторыми изменениями, такими как файл изображения на sd, вместо Ressource, а witthth и heigth получаются из Display Object.

private Bitmap makeBitmap(String path) {

    try {
        final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
        //resource = getResources();

        // Decode image size
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);

        int scale = 1;
        while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) >
                IMAGE_MAX_SIZE) {
            scale++;
        }
        Log.d("TAG", "scale = " + scale + ", orig-width: " + options.outWidth + ", orig-height: " + options.outHeight);

        Bitmap pic = null;
        if (scale > 1) {
            scale--;
            // scale to max possible inSampleSize that still yields an image
            // larger than target
            options = new BitmapFactory.Options();
            options.inSampleSize = scale;
            pic = BitmapFactory.decodeFile(path, options);

            // resize to desired dimensions

            Display display = getWindowManager().getDefaultDisplay();
            Point size = new Point();
            display.getSize(size);
            int width = size.y;
            int height = size.x;

            //int height = imageView.getHeight();
            //int width = imageView.getWidth();
            Log.d("TAG", "1th scale operation dimenions - width: " + width + ", height: " + height);

            double y = Math.sqrt(IMAGE_MAX_SIZE
                    / (((double) width) / height));
            double x = (y / height) * width;

            Bitmap scaledBitmap = Bitmap.createScaledBitmap(pic, (int) x, (int) y, true);
            pic.recycle();
            pic = scaledBitmap;

            System.gc();
        } else {
            pic = BitmapFactory.decodeFile(path);
        }

        Log.d("TAG", "bitmap size - width: " +pic.getWidth() + ", height: " + pic.getHeight());
        return pic;

    } catch (Exception e) {
        Log.e("TAG", e.getMessage(),e);
        return null;
    }

}
Penta
источник
0

Вот код, который я использую, у которого нет проблем с декодированием больших изображений в памяти на Android. Я смог декодировать изображения размером более 20 МБ, если мои входные параметры около 1024x1024. Вы можете сохранить возвращенное растровое изображение в другой файл. Ниже этого метода есть еще один метод, который я также использую для масштабирования изображений до нового растрового изображения. Не стесняйтесь использовать этот код по своему усмотрению.

/*****************************************************************************
 * public decode - decode the image into a Bitmap
 * 
 * @param xyDimension
 *            - The max XY Dimension before the image is scaled down - XY =
 *            1080x1080 and Image = 2000x2000 image will be scaled down to a
 *            value equal or less then set value.
 * @param bitmapConfig
 *            - Bitmap.Config Valid values = ( Bitmap.Config.ARGB_4444,
 *            Bitmap.Config.RGB_565, Bitmap.Config.ARGB_8888 )
 * 
 * @return Bitmap - Image - a value of "null" if there is an issue decoding
 *         image dimension
 * 
 * @throws FileNotFoundException
 *             - If the image has been removed while this operation is
 *             taking place
 */
public Bitmap decode( int xyDimension, Bitmap.Config bitmapConfig ) throws FileNotFoundException
{
    // The Bitmap to return given a Uri to a file
    Bitmap bitmap = null;
    File file = null;
    FileInputStream fis = null;
    InputStream in = null;

    // Try to decode the Uri
    try
    {
        // Initialize scale to no real scaling factor
        double scale = 1;

        // Get FileInputStream to get a FileDescriptor
        file = new File( this.imageUri.getPath() );

        fis = new FileInputStream( file );
        FileDescriptor fd = fis.getFD();

        // Get a BitmapFactory Options object
        BitmapFactory.Options o = new BitmapFactory.Options();

        // Decode only the image size
        o.inJustDecodeBounds = true;
        o.inPreferredConfig = bitmapConfig;

        // Decode to get Width & Height of image only
        BitmapFactory.decodeFileDescriptor( fd, null, o );
        BitmapFactory.decodeStream( null );

        if( o.outHeight > xyDimension || o.outWidth > xyDimension )
        {
            // Change the scale if the image is larger then desired image
            // max size
            scale = Math.pow( 2, (int) Math.round( Math.log( xyDimension / (double) Math.max( o.outHeight, o.outWidth ) ) / Math.log( 0.5 ) ) );
        }

        // Decode with inSampleSize scale will either be 1 or calculated value
        o.inJustDecodeBounds = false;
        o.inSampleSize = (int) scale;

        // Decode the Uri for real with the inSampleSize
        in = new BufferedInputStream( fis );
        bitmap = BitmapFactory.decodeStream( in, null, o );
    }
    catch( OutOfMemoryError e )
    {
        Log.e( DEBUG_TAG, "decode : OutOfMemoryError" );
        e.printStackTrace();
    }
    catch( NullPointerException e )
    {
        Log.e( DEBUG_TAG, "decode : NullPointerException" );
        e.printStackTrace();
    }
    catch( RuntimeException e )
    {
        Log.e( DEBUG_TAG, "decode : RuntimeException" );
        e.printStackTrace();
    }
    catch( FileNotFoundException e )
    {
        Log.e( DEBUG_TAG, "decode : FileNotFoundException" );
        e.printStackTrace();
    }
    catch( IOException e )
    {
        Log.e( DEBUG_TAG, "decode : IOException" );
        e.printStackTrace();
    }

    // Save memory
    file = null;
    fis = null;
    in = null;

    return bitmap;

} // decode

ПРИМЕЧАНИЕ. Методы не имеют ничего общего друг с другом, кроме createScaledBitmap, вызывающего метод decode выше. Примечание ширина и высота могут отличаться от исходного изображения.

/*****************************************************************************
 * public createScaledBitmap - Creates a new bitmap, scaled from an existing
 * bitmap.
 * 
 * @param dstWidth
 *            - Scale the width to this dimension
 * @param dstHeight
 *            - Scale the height to this dimension
 * @param xyDimension
 *            - The max XY Dimension before the original image is scaled
 *            down - XY = 1080x1080 and Image = 2000x2000 image will be
 *            scaled down to a value equal or less then set value.
 * @param bitmapConfig
 *            - Bitmap.Config Valid values = ( Bitmap.Config.ARGB_4444,
 *            Bitmap.Config.RGB_565, Bitmap.Config.ARGB_8888 )
 * 
 * @return Bitmap - Image scaled - a value of "null" if there is an issue
 * 
 */
public Bitmap createScaledBitmap( int dstWidth, int dstHeight, int xyDimension, Bitmap.Config bitmapConfig )
{
    Bitmap scaledBitmap = null;

    try
    {
        Bitmap bitmap = this.decode( xyDimension, bitmapConfig );

        // Create an empty Bitmap which will contain the new scaled bitmap
        // This scaled bitmap should be the size we want to scale the
        // original bitmap too
        scaledBitmap = Bitmap.createBitmap( dstWidth, dstHeight, bitmapConfig );

        float ratioX = dstWidth / (float) bitmap.getWidth();
        float ratioY = dstHeight / (float) bitmap.getHeight();
        float middleX = dstWidth / 2.0f;
        float middleY = dstHeight / 2.0f;

        // Used to for scaling the image
        Matrix scaleMatrix = new Matrix();
        scaleMatrix.setScale( ratioX, ratioY, middleX, middleY );

        // Used to do the work of scaling
        Canvas canvas = new Canvas( scaledBitmap );
        canvas.setMatrix( scaleMatrix );
        canvas.drawBitmap( bitmap, middleX - bitmap.getWidth() / 2, middleY - bitmap.getHeight() / 2, new Paint( Paint.FILTER_BITMAP_FLAG ) );
    }
    catch( IllegalArgumentException e )
    {
        Log.e( DEBUG_TAG, "createScaledBitmap : IllegalArgumentException" );
        e.printStackTrace();
    }
    catch( NullPointerException e )
    {
        Log.e( DEBUG_TAG, "createScaledBitmap : NullPointerException" );
        e.printStackTrace();
    }
    catch( FileNotFoundException e )
    {
        Log.e( DEBUG_TAG, "createScaledBitmap : FileNotFoundException" );
        e.printStackTrace();
    }

    return scaledBitmap;
} // End createScaledBitmap
user560663
источник
расчет мощности для шкалы здесь просто неверен; просто используйте расчет на странице Android Doco.
Толстяк
0
 Bitmap yourBitmap;
 Bitmap resized = Bitmap.createScaledBitmap(yourBitmap, newWidth, newHeight, true);

или:

 resized = Bitmap.createScaledBitmap(yourBitmap,(int)(yourBitmap.getWidth()*0.8), (int)(yourBitmap.getHeight()*0.8), true);
Вайшали Сутария
источник
0

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

Полный код в котлине:

@Throws(IOException::class)
fun File.decodeBitmap(options: BitmapFactory.Options): Bitmap? {
    return inputStream().use {
        BitmapFactory.decodeStream(it, null, options)
    }
}

@Throws(IOException::class)
fun File.decodeBitmapAtLeast(
        @androidx.annotation.IntRange(from = 1) width: Int,
        @androidx.annotation.IntRange(from = 1) height: Int
): Bitmap? {
    val options = BitmapFactory.Options()

    options.inJustDecodeBounds = true
    decodeBitmap(options)

    val ow = options.outWidth
    val oh = options.outHeight

    if (ow == -1 || oh == -1) return null

    val w = ow / width
    val h = oh / height

    if (w > 1 && h > 1) {
        val p = 31 - maxOf(Integer.numberOfLeadingZeros(w), Integer.numberOfLeadingZeros(h))
        options.inSampleSize = 1 shl maxOf(0, p)
    }
    options.inJustDecodeBounds = false
    return decodeBitmap(options)
}
lymoge
источник
-2

Измените размер растрового изображения, используя следующий код

    public static Bitmap decodeFile(File file, int reqWidth, int reqHeight){

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;        
    BitmapFactory.decodeFile(file.getPath(), options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeFile(file.getPath(), options);
   }

    private static int calculateInSampleSize(
    BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) height / (float) reqHeight);
        final int widthRatio = Math.round((float) width / (float) reqWidth);

        // Choose the smallest ratio as inSampleSize value, this will guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
     }

     return inSampleSize;
   }    

То же самое объясняется и в следующем совете

http://www.codeproject.com/Tips/625810/Android-Image-Operations-Using-BitmapFactory

Amol
источник