¿Cómo puedo escalar un mapa de bits en streaming sin leer primero toda la imagen?

Tengo una aplicación de Android que es muy intensiva en imágenes. Actualmente Bitmap.createScaledBitmap() para escalar la imagen a un tamaño deseado. Sin embargo, este método requiere que ya tenga el mapa de bits original en memoria, que puede ser bastante considerable.

¿Cómo puedo escalar un mapa de bits que estoy descargando sin primero escribir toda la cosa a la memoria local o sistema de archivos?

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

También utiliza BitmapFactory.Options.inPurgeable , que parece ser una opción escasamente documentada pero deseable para evitar excepciones OoM cuando se utiliza un montón de mapas de bits. UPDATE: ya no usa inPurgeable, vea esta nota de Romain

Funciona utilizando un BufferedInputStream para leer la información de encabezado de la imagen antes de leer toda la imagen a través de 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 ) {} } } 

Aquí está mi versión, basada en la solución @emmby (gracias hombre!) He incluido una segunda fase en la que tomar el mapa de bits reducido y 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) { } } } 
  • Lectura de un archivo binario en una cadena
  • Android: almacena entradas en el archivo
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.