2012-08-13 14 views
16

Tengo una imagen de 5000 x 4000 px que quiero dibujar en un lienzo.Cargando imágenes grandes sin OutOfMemoryError

Primero intenté cargarlo desde los recursos. Lo puse en /res/drawable.

He utilizado el siguiente método:

InputStream input = getResources().openRawResource(R.drawable.huge_image); 
Drawable d = Drawable.createFromStream(input, "image"); 
d.setBounds(...); 
d.draw(canvas); 

funcionó a las mil maravillas.

En este caso, el InputStream es un AssetManager.AssetInputStream.

Así que ahora quiero cargarlo desde la tarjeta SD.

Esto es lo que he intentado hacer:

File f = new File(path); 
Uri uri = Uri.fromFile(f); 
InputStream input = mContext.getContentResolver().openInputStream(uri); 
Drawable d = Drawable.createFromStream(input, "image"); 

En este caso el InputStream es una FileInputStream y me dieron un OutOfMemoryError al crear el Drawable.

Entonces me pregunto:

¿Hay una manera de cargar la imagen sin conseguir que el error? ¿O hay una manera de convertir un FileInputStream a un AssetInputStream?

Nota:

No quiero cambiar el tamaño de la imagen porque estoy poniendo en práctica la funcionalidad de zoom/pan. Por favor, no me diga que lea Loading Large Bitmaps Efficiently.

Puede consultar la clase completa here. El error ocurre al usar setImageUri().

Aquí es mi registro de errores:

08-13 11:57:54.180: E/AndroidRuntime(23763): FATAL EXCEPTION: main 
08-13 11:57:54.180: E/AndroidRuntime(23763): java.lang.OutOfMemoryError: bitmap size exceeds VM budget 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:468) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:332) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:697) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.graphics.drawable.Drawable.createFromStream(Drawable.java:657) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at com.benitobertoli.largeimagezoom.ZoomImageView.setDrawablefromUri(ZoomImageView.java:187) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at com.benitobertoli.largeimagezoom.ZoomImageView.setImageUri(ZoomImageView.java:588) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at com.benitobertoli.largeimagezoom.TestActivity.onKeyDown(TestActivity.java:30) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.view.KeyEvent.dispatch(KeyEvent.java:1257) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.app.Activity.dispatchKeyEvent(Activity.java:2075) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1673) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.view.ViewRoot.deliverKeyEventToViewHierarchy(ViewRoot.java:2493) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2463) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.view.ViewRoot.handleMessage(ViewRoot.java:1752) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.os.Handler.dispatchMessage(Handler.java:99) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.os.Looper.loop(Looper.java:144) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.app.ActivityThread.main(ActivityThread.java:4937) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at java.lang.reflect.Method.invokeNative(Native Method) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at java.lang.reflect.Method.invoke(Method.java:521) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at dalvik.system.NativeStart.main(Native Method) 

EDIT:

estaba probando mi código en un HTC Desire A8181. Después de que me informaran que el primer fragmento de código no funcionaba en otros dispositivos, lo probé en un Samsung Galaxy S2 y en el Emulador.

Resultados: cuando se carga de recursos, el emulador dieron un OutOfMemoryError, el Galaxy S2 no lanzó una excepción, sino la Drawable regresado era nula.

Supongo que por el momento la única solución es reducir la resolución de la imagen.

+0

He intentado ejecutar su código en m emulator y estoy obteniendo el error OutOfMemory incluso en el método "De Res" :) ¿Qué dispositivo y/o configuración de emulador puede usar sin error? – Joe

Respuesta

13

Tome un vistazo a:

http://developer.android.com/reference/android/graphics/BitmapFactory.html

decodeStream (InputStream is, Rect outPadding, BitmapFactory.Options opts) 

o

decodeFile (String pathName, BitmapFactory.Options opts) 

esta manera se puede cargar todo el mapa de bits y mostrarla en la pantalla.

Tiene que establecer las opciones correctas.

http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html

inSampleSize 

he probado con su código:

BitmapFactory.Options options = new BitmapFactory.Options(); 

options.inSampleSize = 4; 

Bitmap myBitmap = BitmapFactory.decodeFile(mUri.getPath(),options); 
Drawable d = new BitmapDrawable(Resources.getSystem(),myBitmap); 
updateDrawable(d); 

funciona para mí.

+0

Como dije en mi pregunta, no tengo problemas para cargar una Imagen de recursos. Además, no quiero cambiar el tamaño de la imagen. –

+0

lo siento, me olvidé de eso y luego pruebo el método decodeStream (InputStream es, Rect outPadding, BitmapFactory.Options opts) – n3utrino

+0

Repito * No quiero cambiar el tamaño de la imagen *. –

0

No estoy seguro de si es la manera correcta de hacerlo, pero aún así puede resolver su problema.

Intente abrir su imagen en una vista web. Esto le proporcionará una función integrada de acercamiento/alejamiento y panoramización, y no tendrá que preocuparse por abrir ningún flujo de entrada ni nada.

Para abrir una imagen en vista web del activo:

WebView wv = (WebView) findViewById(R.id.webView1); 
    wv.loadUrl("file:///android_asset/abc.png"); 

Puede WebSettings por permitir controles de zoom.

Espero que esto te ayude !!

+0

Gracias por su respuesta. Estoy desarrollando una vista personalizada con detección de gestos y una vista web simplemente no es suficiente. –

3

En mi opinión, la mejor opción es dividir la imagen en pedazos más pequeños. Esto evitará que la aplicación dé una OutOfMemoryException. Aquí está mi código de ejemplo

En primer lugar obtener la imagen de la tarjeta SD una ponerlo en un mapa de bits

Bitmap b = null; 
     try { 
      File sd = new File(Environment.getExternalStorageDirectory() + link_to_image.jpg); 
      FileInputStream fis; 

      fis = new FileInputStream(sd); 

      BitmapFactory.Options options = new BitmapFactory.Options(); 
      options.inPurgeable = true; 
      options.inScaled = false; 

      b = BitmapFactory.decodeStream(fis, null, options); 
      fis.close(); 

     } catch (FileNotFoundException e) { 
      e.printStackTrace(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

Dividir el mapa de bits (b) en trozos más pequeños. En este caso, SPLIT_HEIGHT es 400.

double rest = (image_height + SPLIT_HEIGHT); 
     int i = 0; 
     Bitmap[] imgs = new Bitmap[50]; 

     do { 
      rest -= SPLIT_HEIGHT; 
      if (rest < 0) { 
       // Do nothing 
      } else if (rest < SPLIT_HEIGHT) { 
       imgs[i] = Bitmap.createBitmap(b, 0, (i * SPLIT_HEIGHT), image_width, (int) rest); 
      } else { 
       imgs[i] = Bitmap.createBitmap(b, 0, i * SPLIT_HEIGHT, image_width, SPLIT_HEIGHT); 
      } 

      i++; 

     } while (rest > SPLIT_HEIGHT); 

Ahora tiene una colección de imágenes más pequeñas. Si desea hacerlo perfecly, se puede reciclar este mapa de bits:

// Not needed anymore, free up some memory 
     if (b != null) 
      b.recycle(); 

Desde este punto se puede poner las imágenes más pequeñas en el lienzo. En mi próximo código, coloqué las imágenes en un ScrollView. El código para poner las imágenes en un lienzo es, creo, no muy diferente.

// Add images to scrollview 
     for (int j = 0; j < imgs.length; j++) { 
      Bitmap tmp = imgs[j]; 
      if (tmp != null) { 
       // Add to scrollview 
       ImageView iv = new ImageView(activity); 

       String id = j + "" + articlenumber; 
       iv.setId(Integer.parseInt(id)); 
       iv.setTag(i); 

       iv.setImageBitmap(tmp); 

       RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(globalwidth, tmp.getHeight()); 
       lp.addRule(RelativeLayout.BELOW, previousLongPageImageId); 
       lp.addRule(RelativeLayout.ALIGN_PARENT_LEFT); 

       container.addView(iv, lp); 
       previousLongPageImageId = iv.getId(); 
      } 
     } 

Espero que esto te ayude.

NOTA: La resolución máxima de las imágenes es 2048 * 2048 (restricciones OpenGL o algo así), por lo que siempre tendrá que dividir las imágenes en partes más pequeñas.

+1

Gracias por su respuesta. Incluso antes de intentar dividir el mapa de bits 'b = BitmapFactory.decodeStream (fis, null, options);' ya da un OutOfMemoryError. Además, está dividiendo el mapa de bits en 50 más pequeños, pero aún está cargando todos al mismo tiempo en la memoria, lo que le da el mismo resultado. –

+0

Si configuras android: largeHeap = "true" y luego haces el código de arriba? Como dije en mi nota, tendrá que dividir la imagen en pedazos porque la resolución máxima es 2048 * 2048. Por supuesto, en mi solución también tendrá que dividir la imagen en piezas verticales (el ancho es superior a 2048). – Ceetn

0

Si funcionó cuando cargó desde recursos y ahora no funciona cuando lo carga manualmente, entonces sospecho que tiene algún problema con la administración de recursos, probablemente alguna pérdida de memoria.

¿Se produce OutOfMemoryError siempre al inicio de la aplicación o se produce en un momento posterior? ¿Está en el cambio de configuración? ¿Utiliza campos estáticos (si se utilizan de forma incorrecta, esto a menudo provoca pérdidas de memoria en la aplicación de Android, example with explanation, que se ajusta a su caso)?
Intente utilizar algunas herramientas de creación de perfiles (google puede ayudar a encontrar algo útil).

+0

'OutOfMemoryError' aparece en esta línea' Drawable d = Drawable.createFromStream (input, "image"); 'cuando estoy cargando desde Uri. No estoy usando ningún campo estático. Puede consultar la clase completa [aquí] (https://github.com/BenitoBertoli/ZoomImageView/blob/master/src/com/benitobertoli/largeimagezoom/ZoomImageView.java). Estoy llamando 'setImageUri()'. –

+0

este enlace es para código de trabajo (imagen cargada de recursos). Aparentemente no presionó cambios problemáticos (¿nueva rama?). –

+0

Por favor, vuelva a verificarlo ahora. Agregué un menú. "Desde Res" y "Desde archivo". El error ocurre usando "From File". Tienes que copiar 'huge_image.png' de' res/drawable' a tu tarjeta sd y reemplazar la ruta en tu código. –

Cuestiones relacionadas