Cambiar el tamaño de un archivo de mapa de bits grande a un archivo de salida escalado en Android

218

Tengo un mapa de bits grande (por ejemplo, 3888x2592) en un archivo. Ahora, quiero cambiar el tamaño de ese mapa de bits a 800x533 y guardarlo en otro archivo. Normalmente escalaría el mapa de bits llamando al Bitmap.createBitmapmétodo, pero necesita un mapa de bits de origen como primer argumento, que no puedo proporcionar porque la carga de la imagen original en un objeto de mapa de bits, por supuesto, excedería la memoria (ver aquí , por ejemplo).

Tampoco puedo leer el mapa de bits con, por ejemplo, BitmapFactory.decodeFile(file, options)proporcionar un BitmapFactory.Options.inSampleSize, porque quiero cambiar su tamaño a un ancho y alto exactos. El uso inSampleSizecambiaría el tamaño del mapa de bits a 972x648 (si lo uso inSampleSize=4) o a 778x518 (si lo uso inSampleSize=5, lo que ni siquiera es una potencia de 2).

También me gustaría evitar leer la imagen usando inSampleSize con, por ejemplo, 972x648 en un primer paso y luego cambiar su tamaño exactamente a 800x533 en un segundo paso, porque la calidad sería pobre en comparación con un cambio de tamaño directo de la imagen original.

Para resumir mi pregunta: ¿Hay alguna forma de leer un archivo de imagen grande con 10MP o más y guardarlo en un nuevo archivo de imagen, redimensionado a un nuevo ancho y alto específico, sin obtener una excepción OutOfMemory?

También intenté BitmapFactory.decodeFile(file, options)y configuré manualmente los valores Options.outHeight y Options.outWidth a 800 y 533, pero no funciona de esa manera.

Manuel
fuente
No, outHeight y outWidth son parámetros fuera del método de decodificación. Dicho esto, tengo el mismo problema que tú, y no estoy muy satisfecho con el enfoque de 2 pasos.
rds
a menudo, gracias a Dios, puede usar una línea de código ... stackoverflow.com/a/17733530/294884
Fattie
¡Lectores, tengan en cuenta este control de calidad absolutamente crítico! stackoverflow.com/a/24135522/294884
Fattie
1
Tenga en cuenta que esta pregunta tiene ahora 5 años y la solución completa es ... stackoverflow.com/a/24135522/294884 ¡Salud!
Fattie
2
Ahora hay una documentación oficial sobre ese tema: developer.android.com/training/displaying-bitmaps/…
Vince

Respuestas:

146

No. Me encantaría que alguien me corrigiera, pero acepté el enfoque de carga / cambio de tamaño que intentaste como un compromiso.

Estos son los pasos para cualquiera que esté navegando:

  1. Calcule el máximo posible inSampleSizeque aún produce una imagen más grande que su objetivo.
  2. Cargue la imagen usando BitmapFactory.decodeFile(file, options), pasando inSampleSize como una opción.
  3. Cambiar el tamaño a las dimensiones deseadas usando Bitmap.createScaledBitmap().
Justin
fuente
Traté de evitar eso. Entonces, ¿no hay forma de cambiar el tamaño directamente de una imagen grande en un solo paso?
Manuel
2
No que yo sepa, pero no dejes que eso te impida explorar esto más a fondo.
Justin
Muy bien, tomaré esto por mi respuesta aceptada hasta ahora. Si descubro algún otro método, te lo haré saber.
Manuel
Como se menciona en Psixo una respuesta, es posible que también desee usar android: largeHeap si aún tiene problemas después de usar inSampleSize.
user276648
la variable de mapa de bits se estaba vaciando
Prasad
99

Respuesta de Justin traducida al código (funciona perfecto para mí):

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
fuente
15
Hace que sea difícil de leer cuando usas variables como "b", pero una buena respuesta no obstante.
Oliver Dixon
@Ofir: getImageUri (ruta); ¿Qué tengo que pasar en este método?
Biginner
1
En lugar de (w h) /Math.pow (scale, 2) es más eficiente usar (w h) >> scale.
david.perez
2
No llame System.gc()por favor
gw0
Gracias @Ofir pero esta transformación no conserva la orientación de la imagen: - /
JoeCoolman
43

Estas son las soluciones 'combinadas' de 'Mojo Risin y' Ofir. Esto le dará una imagen redimensionada proporcionalmente con los límites de ancho máximo y alto máximo.

  1. Solo lee metadatos para obtener el tamaño original (options.inJustDecodeBounds)
  2. Utiliza un cambio de tamaño para ahorrar memoria (itmap.createScaledBitmap)
  3. Utiliza una imagen redimensionada con precisión basada en el Bitamp creado anteriormente.

Para mí, ha funcionado bien en 5 imágenes de MegaPixel como se muestra a continuación.

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

¿Por qué no usar la API?

int h = 48; // height in pixels
int w = 48; // width in pixels    
Bitmap scaled = Bitmap.createScaledBitmap(largeBitmap, w, h, true);
Bostone
fuente
21
Porque no resolvería mi problema. Que es: "... necesita un mapa de bits de origen como primer argumento, que no puedo proporcionar porque cargar la imagen original en un objeto de mapa de bits, por supuesto, excedería la memoria". Por lo tanto, tampoco puedo pasar un mapa de bits al método .createScaledBitmap, porque todavía necesitaría cargar una imagen grande en un objeto de mapa de bits primero.
Manuel
2
Correcto. Volví a leer su pregunta y básicamente (si lo entiendo bien) se reduce a "¿puedo cambiar el tamaño de la imagen a las dimensiones exactas sin cargar el archivo original en la memoria?" Si es así, no sé lo suficiente sobre las complejidades del procesamiento de imágenes para responderlo, pero algo me dice que 1. no está disponible desde API, 2. no será de 1 línea. Marcaré esto como favorito; sería interesante ver si usted (u otra persona) resolverá esto.
Bostone
funcionó para mí porque obtengo uri y la conversión a mapa de bits, por lo que escalarlos es fácil para mí 1+ para el más simple.
Hamza
22

Reconociendo la otra excelente respuesta hasta ahora, el mejor código que he visto hasta ahora está en la documentación de la herramienta para tomar fotos.

Consulte la sección titulada "Decodificar una imagen a escala".

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

La solución que propone es una solución de cambio de tamaño y luego escala como las otras aquí, pero es bastante clara.

Copié el código a continuación como una función lista para usar para mayor comodidad.

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
fuente
1
Primero estás dedicando números enteros a lo que dará el resultado. En segundo lugar, el código se bloquea con targetW o targetH siendo 0 (aunque esto no tiene mucho sentido, lo sé). El tercer inSampleSize debería ser una potencia de 2.
cybergen
No me malinterpretes. Definitivamente, esto cargará una imagen, pero si el piso de las entradas está integrado, no lo parece. Y esta tampoco es la respuesta correcta porque la imagen no se escalará como se esperaba. No hará nada hasta que la vista de la imagen sea la mitad del tamaño de la imagen o menor. Entonces no pasa nada hasta que la vista de la imagen sea 1/4 del tamaño de la imagen. ¡Y así sucesivamente con poderes de dos!
cybergen
18

Después de leer estas respuestas y la documentación de Android, aquí está el código para cambiar el tamaño del mapa de bits sin cargarlo en la memoria:

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
fuente
Tenga en cuenta que bmOptions.inPurgeable = true; es obsoleto.
Ravit
6

Cuando tengo mapas de bits grandes y quiero decodificarlos, utilizo lo siguiente

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
fuente
1
Dado que inSampleSize es un número entero, rara vez obtendría el ancho y la altura de píxel exactos que desea obtener. Puede acercarse a veces, pero también puede estar muy lejos, dependiendo de los decimales.
Manuel
Por la mañana, probé su código (publicación anterior en este hilo), pero parece que no funciona, ¿dónde hice mal? Cualquier sugerencia es bienvenida :-)
RRTW
5

Esto puede ser útil para alguien más mirando esta pregunta. Reescribí el código de Justin para permitir que el método reciba el objeto de tamaño objetivo requerido también. Esto funciona muy bien cuando se usa Canvas. Todo el crédito debe ir a JUSTIN por su gran 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;
        }
    }

El código de Justin es MUY efectivo para reducir la sobrecarga de trabajar con mapas de bits grandes.

Mono de la música
fuente
4

No sé si mi solución es la mejor práctica, pero logré cargar un mapa de bits con mi escala deseada utilizando las opciones inDensityy inTargetDensity. inDensityes 0inicialmente cuando no se carga un recurso dibujable, por lo que este enfoque es para cargar imágenes sin recursos.

Las variables imageUri, maxImageSideLengthy contextson parámetros de mi método. Publiqué solo la implementación del método sin el AsyncTask de ajuste para mayor claridad.

            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;
cibergen
fuente
2
¡Muy agradable! El uso de inDensity en lugar de Bitmap.createScaledBitmap me ahorró una gran cantidad de memoria. Aún mejor combinado con inSamplesize.
Ostkontentitan
2

Teniendo en cuenta que desea cambiar el tamaño al tamaño exacto y desea mantener tanta calidad como sea necesario, creo que debería intentar esto.

  1. Descubra el tamaño de la imagen redimensionada con la llamada de BitmapFactory.decodeFile y proporcione checkSizeOptions.inJustDecodeBounds
  2. Calcular el máximo posible en SampleSize que puede usar en su dispositivo para no exceder la memoria. bitmapSizeInBytes = 2 * ancho * alto; En general, para su imagen, inSampleSize = 2 estaría bien, ya que solo necesitará 2 * 1944x1296) = 4.8Mbб, que deberían ser pies en la memoria
  3. Use BitmapFactory.decodeFile con inSampleSize para cargar el Bitmap
  4. Escale el mapa de bits al tamaño exacto.

Motivación: el escalado de múltiples pasos podría proporcionarle una imagen de mayor calidad, sin embargo, no hay garantía de que funcione mejor que usar inSampleSize alto. En realidad, creo que también puede usar inSampleSize como 5 (no Pow de 2) para tener una escala directa en una operación. O simplemente use 4 y luego puede usar esa imagen en la interfaz de usuario. si lo envía al servidor, puede escalar al tamaño exacto en el lado del servidor, lo que le permite utilizar técnicas de escalado avanzadas.

Notas: si el mapa de bits cargado en el paso 3 es al menos 4 veces más grande (por lo que 4 * targetWidth <ancho), probablemente pueda usar varios cambios de tamaño para lograr una mejor calidad. al menos eso funciona en java genérico, en android no tienes la opción de especificar la interpolación utilizada para escalar http://today.java.net/pub/a/today/2007/04/03/perils-of- image-getscaledinstance.html

Andrey Chorniy
fuente
2

Usé un 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());

Intenté que la imagen original sea 1230 x 1230, y obtuve un mapa de bits que dice que es 330 x 330.
Y si probé 2590 x 3849, obtendré OutOfMemoryError.

Lo rastreé, todavía arroja OutOfMemoryError en la línea "BitmapFactory.decodeStream (es, nulo, opciones);", si el mapa de bits original es demasiado grande ...

RRTW
fuente
2

El código anterior hizo un poco más limpio. InputStreams finalmente tiene una envoltura cerrada para garantizar que también se cierren:

* Nota de
entrada: InputStream es, int w, int h
Salida: mapa de bits

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

    }
usuario1327738
fuente
1

Para escalar la imagen de la manera "correcta", sin omitir ningún píxel, tendría que conectar el decodificador de imágenes para realizar el muestreo descendente fila por fila. Android (y la biblioteca de Skia que lo subyace) no proporciona tales ganchos, por lo que tendrías que rodar el tuyo. Suponiendo que está hablando de imágenes jpeg, su mejor opción sería usar libjpeg directamente, en C.

Dadas las complejidades involucradas, usar la submuestra de dos pasos y luego reescalar es probablemente el mejor para las aplicaciones de tipo de vista previa de imagen.

D0SBoots
fuente
1

Si realmente desea cambiar el tamaño de un paso, probablemente pueda cargar un mapa de bits completo si android: largeHeap = true, pero como puede ver, esto no es realmente recomendable.

De documentos: android: largeHeap Si los procesos de su aplicación deben crearse con un gran montón de Dalvik. Esto se aplica a todos los procesos creados para la aplicación. Solo se aplica a la primera aplicación cargada en un proceso; Si está utilizando una ID de usuario compartida para permitir que varias aplicaciones utilicen un proceso, todas deben usar esta opción de manera consistente o tendrán resultados impredecibles. La mayoría de las aplicaciones no deberían necesitar esto, sino que deberían centrarse en reducir el uso general de la memoria para mejorar el rendimiento. Habilitar esto tampoco garantiza un aumento fijo en la memoria disponible, porque algunos dispositivos están limitados por su memoria total disponible.

Igor Čordaš
fuente
0

Esto funcionó para mí. La función obtiene una ruta a un archivo en la tarjeta SD y devuelve un mapa de bits en el tamaño máximo visualizable. El código es de Ofir con algunos cambios como el archivo de imagen en sd en lugar de un Ressource y el ingenio y la altura se obtienen del objeto de visualización.

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

Aquí está el código que uso que no tiene ningún problema para decodificar imágenes grandes en la memoria de Android. He podido decodificar imágenes de más de 20 MB siempre que mis parámetros de entrada sean de 1024x1024. Puede guardar el mapa de bits devuelto en otro archivo. Debajo de este método hay otro método que también uso para escalar imágenes a un nuevo mapa de bits. Siéntase libre de usar este código como lo desee.

/*****************************************************************************
 * 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: Los métodos no tienen nada que ver entre sí, excepto createScaledBitmap invoca el método de decodificación anterior. El ancho y la altura de la nota pueden cambiar de la imagen 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
fuente
el cálculo de potencia para la escala simplemente está mal aquí; solo usa el cálculo en la página de Android doco.
Fattie
0
 Bitmap yourBitmap;
 Bitmap resized = Bitmap.createScaledBitmap(yourBitmap, newWidth, newHeight, true);

o:

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

yo suelo Integer.numberOfLeadingZeros para calcular el mejor tamaño de muestra, mejor rendimiento.

Código completo en 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
fuente
-2

Cambiar el tamaño del mapa de bits con el siguiente 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;
   }    

Lo mismo también se explica en el siguiente consejo / truco

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

Amol
fuente