2011-08-13 10 views
10

Tengo una aplicación de Android que es muy intensiva en imágenes. Actualmente estoy usando Bitmap.createScaledBitmap() para escalar la imagen al tamaño deseado. Sin embargo, este método requiere que ya tenga el mapa de bits original en la memoria, que puede ser bastante considerable.¿Cómo escalo un mapa de bits en tiempo real sin leer primero toda la imagen?

¿Cómo puedo escalar un mapa de bits que estoy descargando sin antes escribir todo en la memoria local o en el sistema de archivos?

Respuesta

23

Este método leerá la información del encabezado de la imagen para determinar su tamaño, luego leerá la imagen y la escalará al tamaño deseado en su lugar sin asignar memoria para la imagen de tamaño original.

También usa BitmapFactory.Options.inPurgeable, que parece ser una opción escasamente documentada pero deseable para evitar excepciones de OoM cuando se utilizan lotes de mapas de bits. ACTUALIZACIÓN: ya no se utiliza inPurgeable, ver this note de Romain

Funciona mediante el uso de un BufferedInputStream para leer la información del encabezado de la imagen antes de leer toda la imagen en medio del InputStream.

/** 
* Read the image from the stream and create a bitmap scaled to the desired 
* size. Resulting bitmap will be at least as large as the 
* desired minimum specified dimensions and will keep the image proportions 
* correct during scaling. 
*/ 
protected Bitmap createScaledBitmapFromStream(InputStream s, int minimumDesiredBitmapWith, int minimumDesiredBitmapHeight) { 

    final BufferedInputStream is = new BufferedInputStream(s, 32*1024); 
    try { 
     final Options decodeBitmapOptions = new Options(); 
     // For further memory savings, you may want to consider using this option 
     // decodeBitmapOptions.inPreferredConfig = Config.RGB_565; // Uses 2-bytes instead of default 4 per pixel 

     if(minimumDesiredBitmapWidth >0 && minimumDesiredBitmapHeight >0) { 
      final Options decodeBoundsOptions = new Options(); 
      decodeBoundsOptions.inJustDecodeBounds = true; 
      is.mark(32*1024); // 32k is probably overkill, but 8k is insufficient for some jpgs 
      BitmapFactory.decodeStream(is,null,decodeBoundsOptions); 
      is.reset(); 

      final int originalWidth = decodeBoundsOptions.outWidth; 
      final int originalHeight = decodeBoundsOptions.outHeight; 

      // inSampleSize prefers multiples of 2, but we prefer to prioritize memory savings 
      decodeBitmapOptions.inSampleSize= Math.max(1,Math.min(originalWidth/minimumDesiredBitmapWidth, originalHeight/minimumDesiredBitmapHeight)); 

     } 

     return BitmapFactory.decodeStream(is,null,decodeBitmapOptions); 

    } catch(IOException e) { 
     throw new RuntimeException(e); // this shouldn't happen 
    } finally { 
     try { 
      is.close(); 
     } catch(IOException ignored) {} 
    } 

} 
+1

También puede ser necesario para realizar una llamada explícita GC antes de llamar BitmapFactory.decodeStream, según esta respuesta: http://stackoverflow.com/questions/6718855/using-caching-to-improve-scrolling-performance -in-an-android-listview-with-images/7245318 # 7245318 – emmby

+0

probablemente tendría sentido usar new/close en lugar de mark/reset en FileInputStream. Pero la idea es buena. – kellogs

+1

¿Por qué obtengo 'java.io.IOException: Mark ha sido invalidado' en' is.reset(); ' – sancho21

1

Aquí está mi versión, basado en la solución @emmby (gracias hombre!) He incluido una segunda fase donde se toma la reducción de mapa de bits y la escala de nuevo para coincidir exactamente con las dimensiones deseadas. Mi versión toma una ruta de archivo en lugar de una secuencia.

protected Bitmap createScaledBitmap(String filePath, int desiredBitmapWith, int desiredBitmapHeight) throws IOException, FileNotFoundException { 
    BufferedInputStream imageFileStream = new BufferedInputStream(new FileInputStream(filePath)); 
    try { 
     // Phase 1: Get a reduced size image. In this part we will do a rough scale down 
     int sampleSize = 1; 
     if (desiredBitmapWith > 0 && desiredBitmapHeight > 0) { 
      final BitmapFactory.Options decodeBoundsOptions = new BitmapFactory.Options(); 
      decodeBoundsOptions.inJustDecodeBounds = true; 
      imageFileStream.mark(64 * 1024); 
      BitmapFactory.decodeStream(imageFileStream, null, decodeBoundsOptions); 
      imageFileStream.reset(); 
      final int originalWidth = decodeBoundsOptions.outWidth; 
      final int originalHeight = decodeBoundsOptions.outHeight; 
      // inSampleSize prefers multiples of 2, but we prefer to prioritize memory savings 
      sampleSize = Math.max(1, Math.max(originalWidth/desiredBitmapWith, originalHeight/desiredBitmapHeight)); 
     } 
     BitmapFactory.Options decodeBitmapOptions = new BitmapFactory.Options(); 
     decodeBitmapOptions.inSampleSize = sampleSize; 
     decodeBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565; // Uses 2-bytes instead of default 4 per pixel 

     // Get the roughly scaled-down image 
     Bitmap bmp = BitmapFactory.decodeStream(imageFileStream, null, decodeBitmapOptions); 

     // Phase 2: Get an exact-size image - no dimension will exceed the desired value 
     float ratio = Math.min((float)desiredBitmapWith/ (float)bmp.getWidth(), (float)desiredBitmapHeight/ (float)bmp.getHeight()); 
     int w =(int) ((float)bmp.getWidth() * ratio); 
     int h =(int) ((float)bmp.getHeight() * ratio); 
     return Bitmap.createScaledBitmap(bmp, w,h, true); 

    } catch (IOException e) { 
     throw e; 
    } finally { 
     try { 
      imageFileStream.close(); 
     } catch (IOException ignored) { 
     } 
    } 
} 
Cuestiones relacionadas