Redimensionar um arquivo bitmap grande para um arquivo de saída dimensionado no Android

218

Eu tenho um bitmap grande (digamos 3888x2592) em um arquivo. Agora, quero redimensionar esse bitmap para 800x533 e salvá-lo em outro arquivo. Normalmente, eu escalaria o bitmap chamando o Bitmap.createBitmapmétodo, mas ele precisa de um bitmap de origem como o primeiro argumento, o que não posso fornecer, pois carregar a imagem original em um objeto Bitmap excederia naturalmente a memória (veja aqui , por exemplo).

Também não consigo ler o bitmap, por exemplo, BitmapFactory.decodeFile(file, options)fornecendo um BitmapFactory.Options.inSampleSize, porque quero redimensioná-lo para uma largura e altura exatas. Usar inSampleSizeredimensionaria o bitmap para 972x648 (se eu usar inSampleSize=4) ou para 778x518 (se eu usar inSampleSize=5, o que não é nem uma potência de 2).

Também gostaria de evitar a leitura da imagem usando o inSampleSize com, por exemplo, 972x648 em uma primeira etapa e redimensioná-la para exatamente 800x533 em uma segunda etapa, porque a qualidade seria baixa em comparação com o redimensionamento direto da imagem original.

Para resumir minha pergunta: Existe uma maneira de ler um arquivo de imagem grande com 10MP ou mais e salvá-lo em um novo arquivo de imagem, redimensionado para uma nova largura e altura específica, sem obter uma exceção OutOfMemory?

Também tentei BitmapFactory.decodeFile(file, options)definir os valores Options.outHeight e Options.outWidth manualmente para 800 e 533, mas não funciona dessa maneira.

Manuel
fonte
Não, os parâmetros outHeight e outWidth são out do método decode. Dito isto, tenho o mesmo problema que você e não estou muito satisfeito com a abordagem de duas etapas.
Rd5
muitas vezes, graças a Deus, você pode usar uma linha de código .. stackoverflow.com/a/17733530/294884
Fattie
Leitores, observe este controle de qualidade absolutamente crítico !!! stackoverflow.com/a/24135522/294884
Fattie
1
Por favor, note que esta questão agora tem 5 anos e a solução completa é .. stackoverflow.com/a/24135522/294884 Cheers!
Fattie
2
Agora existe uma documentação oficial sobre esse tópico: developer.android.com/training/displaying-bitmaps/…
Vince

Respostas:

146

Não . Adoraria que alguém me corrigisse, mas aceitei a abordagem de carregamento / redimensionamento que você tentou como um compromisso.

Aqui estão as etapas para quem estiver navegando:

  1. Calcule o máximo possível inSampleSizeque ainda produz uma imagem maior que a sua meta.
  2. Carregue a imagem usando BitmapFactory.decodeFile(file, options), passando inSampleSize como uma opção.
  3. Redimensione para as dimensões desejadas usando Bitmap.createScaledBitmap().
Justin
fonte
Eu tentei evitar isso. Portanto, não há como redimensionar diretamente uma imagem grande em apenas uma etapa?
Manuel
2
Não é do meu conhecimento, mas não deixe que isso o impeça de explorar isso ainda mais.
2626 Justin Justin
Tudo bem, vou levar isso para a minha resposta aceita até agora. Se eu descobrir outros métodos, eu o informarei.
Manuel
Como o PSIXO mencionado em uma resposta, você também pode usar o android: largeHeap se ainda tiver problemas após usar o inSampleSize.
user276648
variável bitmap estava ficando vazia
Prasad
99

Justin resposta traduzida em código (funciona perfeito para mim):

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;
}
Ofir
fonte
15
Torna difícil ler quando você usa variáveis ​​como "b", mas uma boa resposta é a menor.
Oliver Dixon
@Ofir: getImageUri (caminho); o que eu tenho que passar nesse método?
Biginner 28/05
1
Em vez de (w h) /Math.pow (scale, 2), é mais eficiente usar (w h) >> a escala.
david.perez 23/09
2
Não chame System.gc()por favor
gw0
Não graças @Ofir mas essa transformação não conservar a orientação da imagem: - /
JoeCoolman
43

Estas são as soluções de 'Mojo Risin e' Ofir "combinadas". Isso fornecerá uma imagem redimensionada proporcionalmente com os limites de largura e altura máx.

  1. Ele apenas lê metadados para obter o tamanho original (options.inJustDecodeBounds)
  2. Ele usa um redimensionamento expandido para economizar memória (itmap.createScaledBitmap)
  3. Ele usa uma imagem redimensionada com base no Bitamp áspero criado anteriormente.

Para mim, ele tem funcionado bem em 5 imagens MegaPixel abaixo.

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
fonte
23

Por que não usar a API?

int h = 48; // height in pixels
int w = 48; // width in pixels    
Bitmap scaled = Bitmap.createScaledBitmap(largeBitmap, w, h, true);
Bostone
fonte
21
Porque isso não resolveria o meu problema. O que é: "... ele precisa de um bitmap de origem como o primeiro argumento, o que não posso fornecer, porque carregar a imagem original em um objeto Bitmap naturalmente excederia a memória." Portanto, também não posso passar um Bitmap para o método .createScaledBitmap, porque eu ainda precisaria carregar uma imagem grande em um objeto Bitmap primeiro.
Manuel
2
Certo. Reli sua pergunta e, basicamente (se entender bem), ela se resume a "posso redimensionar a imagem para dimensões exatas sem carregar o arquivo original na memória?" Se sim - eu não sei o suficiente sobre os meandros do processamento de imagens para respondê-lo, mas algo me diz que 1. não está disponível na API, 2. não será de 1 linha. Vou marcar isso como favorito - seria interessante ver se você (ou outra pessoa) resolverá isso.
Bostone 6/09/10
funcionou para mim porque estou obtendo uri e convertendo em bitmap, então é fácil dimensioná-los 1+ para o mais simples.
Hamza
22

Reconhecendo a outra excelente resposta até agora, o melhor código que já vi para isso está na documentação da ferramenta de tirar fotos.

Consulte a seção "Decodificar uma imagem em escala".

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

A solução que ela propõe é uma solução redimensionada e dimensionada como as outras aqui, mas é bem legal.

Copiei o código abaixo como uma função pronta para uso por conveniência.

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
fonte
1
Primeiro, você está dividindo números inteiros para definir o resultado. Segundo, o código falha com targetW ou targetH sendo 0 (embora isso não faça muito sentido, eu sei). O terceiro inSampleSize deve ter uma potência de 2. #
cybergen
Não me interpretem mal. Isso definitivamente carregará uma imagem, mas se o revestimento das entradas for recuado, não parecerá. E essa também definitivamente não é a resposta certa, porque a imagem não será dimensionada conforme o esperado. Não fará nada até que a visualização da imagem tenha metade do tamanho da imagem ou menor. Então, nada acontece até que a visualização da imagem seja 1/4 do tamanho da imagem. E assim por diante com poderes de dois!
cybergen
18

Depois de ler essas respostas e a documentação do Android, veja o código para redimensionar o bitmap sem carregá-lo na memória:

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
fonte
Observe que bmOptions.inPurgeable = true; está obsoleto.
Ravit 06/06
6

Quando tenho bitmaps grandes e quero decodificá-los redimensionados, uso o seguinte

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);
Mojo Risin
fonte
1
Como inSampleSize é um número inteiro, você raramente obtém a largura e a altura exatas dos pixels que deseja obter. Às vezes você pode se aproximar, mas também pode estar longe disso, dependendo das casas decimais.
Manuel
Bom dia, tentei o seu código (poste acima neste tópico), mas parece não funcionar, onde fiz de errado? Qualquer
sugestão
5

Isso pode ser útil para alguém que está analisando essa pergunta. Reescrevi o código de Justin para permitir que o método recebesse também o objeto de tamanho desejado. Isso funciona muito bem ao usar o Canvas. Todo o crédito deve ser atribuído ao JUSTIN pelo seu excelente código inicial.

    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;
        }
    }

O código de Justin é MUITO eficaz na redução da sobrecarga de trabalho com bitmaps grandes.

Macaco da música
fonte
4

Não sei se minha solução é a melhor prática, mas consegui carregar um bitmap com o dimensionamento desejado usando as opções inDensitye inTargetDensity. inDensityé 0inicialmente quando não está carregando um recurso extraível, portanto, essa abordagem é para carregar imagens sem recurso.

As variáveis imageUri, maxImageSideLengthe contextsão parâmetros de meu método. Postei apenas a implementação do método sem o AsyncTask de quebra para maior clareza.

            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
fonte
2
Muito agradável! Usar inDensity em vez de Bitmap.createScaledBitmap me salvou muita pilha de memória. Ainda melhor combinado com inSamplesize.
Ostkontentitan
2

Considerando que você deseja redimensionar para o tamanho exato e manter a qualidade necessária, acho que você deve tentar isso.

  1. Descubra o tamanho da imagem redimensionada com a chamada BitmapFactory.decodeFile e forneça o checkSizeOptions.inJustDecodeBounds
  2. Calcular o máximo possível deSampleSize que você pode usar no seu dispositivo para não exceder a memória. bitmapSizeInBytes = 2 * largura * altura; Geralmente, para a sua imagem inSampleSize = 2 seria bom, pois você precisará de apenas 2 * 1944x1296) = 4.8Mbб, o que deve ter os pés na memória
  3. Use BitmapFactory.decodeFile com inSampleSize para carregar o Bitmap
  4. Escale o bitmap para o tamanho exato.

Motivação: o dimensionamento de várias etapas pode fornecer uma imagem de qualidade mais alta, no entanto, não há garantia de que funcione melhor do que usar alta inSampleSize. Na verdade, acho que você também pode usar inSampleSize como 5 (não pow de 2) para ter dimensionamento direto em uma operação. Ou basta usar 4 e, em seguida, você pode usar essa imagem na interface do usuário. se você o enviar para o servidor -, poderá dimensionar para o tamanho exato no lado do servidor, o que permite usar técnicas avançadas de dimensionamento.

Notas: se o Bitmap carregado na etapa 3 for pelo menos 4 vezes maior (portanto, 4 * targetWidth <width), você provavelmente poderá usar vários redimensionamentos para obter melhor qualidade. pelo menos, que funciona em java genérico, no android você não tem a opção de especificar a interpolação usada para dimensionar http://today.java.net/pub/a/today/2007/04/03/perils-of- image-getscaledinstance.html

Andrey Chorniy
fonte
2

Eu usei código como este:

  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());

Eu tentei a imagem original é 1230 x 1230 e o bitmap diz que é 330 x 330.
E se tentei 2590 x 3849, obterei o OutOfMemoryError.

Eu rastreei, ele ainda joga OutOfMemoryError na linha "BitmapFactory.decodeStream (is, null, options);", se o bitmap original é muito grande ...

RRTW
fonte
2

O código acima tornou um pouco mais limpo. Os InputStreams finalmente fecharam o invólucro para garantir que também fiquem fechados:

* Nota
Input: InputStream é, int w, int h
Saída: Bitmap

    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
fonte
1

Para dimensionar a imagem da maneira "correta", sem pular nenhum pixel, você teria que se conectar ao decodificador de imagem para realizar a amostragem descendente linha por linha. O Android (e a biblioteca Skia que o sustenta) não fornece esses ganchos, então você teria que usar o seu. Supondo que você esteja falando de imagens jpeg, sua melhor aposta seria usar a libjpeg diretamente, em C.

Dadas as complexidades envolvidas, o uso da subamostra de duas etapas e depois da nova escala é provavelmente o melhor para aplicativos do tipo visualização de imagem.

D0SBoots
fonte
1

Se você realmente quiser redimensionar uma etapa, provavelmente poderá carregar bitmap inteiro se android: largeHeap = true, mas como você pode ver, isso não é realmente recomendável.

Do docs: android: largeHeap Se os processos do seu aplicativo devem ser criados com um grande heap Dalvik. Isso se aplica a todos os processos criados para o aplicativo. Aplica-se apenas ao primeiro aplicativo carregado em um processo; se você estiver usando um ID de usuário compartilhado para permitir que vários aplicativos usem um processo, todos deverão usar essa opção de forma consistente ou terão resultados imprevisíveis. A maioria dos aplicativos não precisa disso e deve se concentrar em reduzir o uso geral de memória para melhorar o desempenho. Habilitar isso também não garante um aumento fixo na memória disponível, porque alguns dispositivos são limitados por sua memória total disponível.

Igor Čordaš
fonte
0

Isso funcionou para mim. A função obtém um caminho para um arquivo no cartão SD e retorna um Bitmap no tamanho máximo de exibição. O código é do Ofir com algumas alterações, como o arquivo de imagem no sd, em vez de um Ressource e a witdth e heigth são obtidas do objeto de exibição.

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
fonte
0

Aqui está o código que uso, sem problemas na decodificação de imagens grandes na memória do Android. Consegui decodificar imagens maiores que 20 MB, desde que meus parâmetros de entrada estejam em torno de 1024x1024. Você pode salvar o bitmap retornado em outro arquivo. Abaixo desse método, há outro método que também uso para dimensionar imagens para um novo bitmap. Sinta-se livre para usar este código como desejar.

/*****************************************************************************
 * 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

NOTA: Os métodos não têm nada a ver um com o outro, exceto o método createScaledBitmap chama o método de decodificação acima. Observe que a largura e a altura podem mudar da imagem original.

/*****************************************************************************
 * 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
fonte
o cálculo de potência para a balança está simplesmente errado aqui; basta usar o cálculo na página de documentos do Android.
Fattie
0
 Bitmap yourBitmap;
 Bitmap resized = Bitmap.createScaledBitmap(yourBitmap, newWidth, newHeight, true);

ou:

 resized = Bitmap.createScaledBitmap(yourBitmap,(int)(yourBitmap.getWidth()*0.8), (int)(yourBitmap.getHeight()*0.8), true);
Vaishali Sutariya
fonte
0

eu uso Integer.numberOfLeadingZeros para calcular o melhor tamanho da amostra, melhor desempenho.

Código completo no kotlin:

@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
fonte
-2

Redimensione o bitmap usando o seguinte código

    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;
   }    

O mesmo também é explicado na seguinte dica / truque

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

Amol
fonte