I have a 5000 x 4000 px image which I want to draw onto a canvas.
First I tried to load it from resources.
I put it in /res/drawable
.
I used the following method:
InputStream input = getResources().openRawResource(R.drawable.huge_image);
Drawable d = Drawable.createFromStream(input, "image");
d.setBounds(...);
d.draw(canvas);
It worked like a charm.
In this case the InputStream
is an AssetManager.AssetInputStream
.
So now I want to load it from the sdcard.
Here's what I tried to do:
File f = new File(path);
Uri uri = Uri.fromFile(f);
InputStream input = mContext.getContentResolver().openInputStream(uri);
Drawable d = Drawable.createFromStream(input, "image");
In this case the InputStream
is a FileInputStream
and I got an OutOfMemoryError
when creating the Drawable
.
So I'm wondering:
Is there a way to load the image without getting that error? Or is there a way to convert a FileInputStream
to an AssetInputStream
?
Note:
I don't want to resize the image because I'm implementing zoom/pan functionality. Please don't tell me to read Loading Large Bitmaps Efficiently.
You can check out the full class here. The error occurs when using setImageUri()
.
Here's my Error Log:
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:
I was testing my code on an HTC Desire A8181. After being told that the first code snippet wasn't working on some other devices, I tested on a Samsung Galaxy S2 and on the Emulator.
Results:
When loading from resources, the emulator gave an OutOfMemoryError
, the Galaxy S2 didn't throw an exception but the returned Drawable
was null.
So I guess for the time being the only solution is to downsample the image.
If it worked when you loaded from resources and now it doesn't work when you load manually then I suspect that you have some problem with resource management, probably some memory leak.
Does
OutOfMemoryError
occur always at the start of application or it happens in later time? Is it on configuration change? Do you use static fields (if used incorrectly this often leads to memory leaks in Android application, example with explanation this fits to your case)?Try use some profiling tools (google can help to find something useful).
Take a look at:
http://developer.android.com/reference/android/graphics/BitmapFactory.html
or
this way you can load your whole bitmap and show it on screen.
You need to set the correct options.
http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html
I tried it with your code:
works for me.
I am not sure whether it is the correct of way of doing this but still this can solve your problem.
Try opening your image in a webview. This will provide you in-built zoom-in/out and panning feature and you dont have to worry about opening any input stream or anything else.
To open an image in webview from assets:
You can WebSettings for allowing zoom controls.
Hope this will help you!!
In my opinion, the best option is to split the image into smaller pieces. This will prevent the application to give an OutOfMemoryException. Here is my example code
First get the image from the SD Card an put it into an Bitmap
Split the bitmap (b) into smaller pieces. In this case SPLIT_HEIGHT is 400.
Now you have a collection of smaller images. If you want to do it perfecly, you can recycle this bitmap:
From this point you can put the smaller images onto the canvas. In my next code I put the images into an ScrollView. The code for putting the images on a Canvas is I think not very different.
I hope this will help you.
NOTE: The maximum resolution for images is 2048*2048 (OpenGL restrictions or something), so you will always have to split images into smaller pieces.