¿Cómo escalo un bitmap en tiempo real sin leer primero toda la imagen?

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 bitmap original en la memoria, que puede ser bastante considerable.

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

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 sin asignar memoria para la imagen de tamaño original.

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

Funciona mediante el uso de BufferedInputStream para leer la información del 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, amigo!). He incluido una segunda fase en la que se toma el bitmap reducido y se escala nuevamente para que coincida 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) { } } }