218 votos

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

Tengo un mapa de bits grande (digamos 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 a Bitmap.createBitmap pero necesita un mapa de bits de origen como primer argumento, que no puedo proporcionar porque cargar la imagen original en un objeto Bitmap por supuesto excedería la memoria (ver aquí por ejemplo).

Tampoco puedo leer el mapa de bits con, por ejemplo, BitmapFactory.decodeFile(file, options) , proporcionando una BitmapFactory.Options.inSampleSize porque quiero cambiar el tamaño a una anchura y altura exactas. Utilizando inSampleSize redimensionaría el mapa de bits a 972x648 (si utilizo inSampleSize=4 ) o a 778x518 (si uso inSampleSize=5 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 redimensionarla a exactamente 800x533 en un segundo paso, porque la calidad sería pobre comparada con un redimensionamiento directo de la imagen original.

Para resumir mi pregunta: ¿Existe una manera de leer un archivo de imagen grande con 10MP o más y guardarlo en un nuevo archivo de imagen, redimensionado a una nueva anchura y altura específicas, sin obtener una excepción OutOfMemory?

También probé BitmapFactory.decodeFile(file, options) y estableciendo manualmente los valores Options.outHeight y Options.outWidth a 800 y 533, pero no funciona así.

146voto

Justin Puntos 9636

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

Aquí están los pasos para cualquier persona que esté navegando:

  1. Calcular el máximo posible inSampleSize que sigue dando una imagen más grande que su objetivo.
  2. Cargar la imagen con BitmapFactory.decodeFile(file, options) pasando como opción inSampleSize.
  3. Cambie el tamaño a las dimensiones deseadas utilizando Bitmap.createScaledBitmap() .

100voto

Ofir Puntos 521

La respuesta de Justin traducida al código (me funciona perfectamente):

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

43voto

blubl Puntos 181

Esta es la solución de 'Mojo Risin' y 'Ofir' "combinada". Esto le dará una imagen redimensionada proporcionalmente con los límites de la anchura máxima y la altura máxima.

  1. Sólo lee los metadatos para obtener el tamaño original (options.inJustDecodeBounds)
  2. Utiliza un redimensionamiento rápido para ahorrar memoria (itmap.createScaledBitmap)
  3. Utiliza una imagen redimensionada con precisión basada en el borrador de Bitamp creado anteriormente.

En mi caso, ha funcionado bien con imágenes de 5 megapíxeles e inferiores.

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

23voto

Bostone Puntos 14208

¿Por qué no utilizar la API?

int h = 48; // height in pixels
int w = 48; // width in pixels    
Bitmap scaled = Bitmap.createScaledBitmap(largeBitmap, w, h, true);

22voto

Alex Puntos 3933

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

Consulte la sección titulada "Decodificar una imagen escalada".

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

La solución que propone es una solución de redimensionar y luego escalar como las otras aquí, pero está bastante bien.

He copiado el código de abajo como una función lista para usar por conveniencia.

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

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X