BitmapFactory.decodeFile causes outOfMemoryError only when I choose the image from the phone gallery, not when I choose the image from taking the photo with the camera intent. I actually avoid using decodeFile when using the camera intent. Can I use that same approach when choosing from the gallery (getting data from the intent)? Here is my code for getting the image in both ways:
If image was chosen from the device' gallery:
if (requestCode == 1)
{
if (intent != null && resultcode == RESULT_OK)
{
ImageHelper ih = new ImageHelper();
mProfilePicPath = ih.getSelectedImageFilePathFromGallery(this, intent);
Bitmap portraitPhoto = ih.getChosenImageFromGallery(mProfilePicPath);
try{
ih.saveImage("Profile pics", portraitPhoto, this);
ImageHelper.getChosenImageFromGallery:
public Bitmap getChosenImageFromGallery(String imagePath) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, options);
options.inSampleSize = DressingRoomActivity.calculateInSampleSize(options, 500, 500);
options.inJustDecodeBounds = false;
Bitmap portraitPhoto = ImageHelper.convertToPortraitOrientation(options, imagePath);
return portraitPhoto;
}
If image was chosen by taking photo using camera:
else if ((requestCode == CAMERA_REQUEST)&& (resultcode == Activity.RESULT_OK)){
Bundle extras = intent.getExtras();
Uri selectedImage = intent.getData();
if (extras != null) {
ImageHelper ih = new ImageHelper();
mProfilePicPath = ih.getFilePathFromCameraPhoto(this, selectedImage);
Bitmap photo = (Bitmap) intent.getExtras().get("data");
try{
ih.saveImage("Profile pics", photo, this);
As you can see, to grab the image from the gallery and get the Bitmap from it, I have to create the Bitmap to be only 500 x 500 pixels with this code options.inSampleSize = DressingRoomActivity.calculateInSampleSize(options, 500, 500); Otherwise it causes an outOfMemoryError on the line Bitmap bmp = BitmapFactory.decodeFile(path, options); Which is in the ImageHelper.convertToPortraitOrientation(options, imagePath); method. This means the user gets AS IS quality in their image from the camera, and 500px quality from their image in the gallery.
My questions: 1) Is there any way to not make my image quality 500px when choosing from gallery? Like avoid this line somehow: Bitmap bmp = BitmapFactory.decodeFile(path, options); or some other way?
2) With the gallery I had to convert to portrait which is only a minor annoyance but why is that? It flips the photo sideways if I don't.
Here is a complete tuto on how to Display Bitmaps Efficiently from the official doc. I had the same problem as you. What i did was to read and understand all the lessons from the given page before returning back to my codes. Try that, you will get things clearer in your head.
Edit
Your app is crashing because you are running the Bitmap.decode() method in the main thread. Use another thread (with a simple AsyncTask) to do that.
As i said before, there is a nice tuto on how to handle bitmap off UI Thread
There is 2 possible solutions to overcome outOfMemory exception:
Solution 1: you should use weakReference variables like.
WeakReference<Bitmap> weakReferenceBitmap=new
WeakReference<Bitmap>(bitmap);
GC clear weakReference variable more efficient as compare to other.
Solution2: you can use largeHeap property in your manifest to require more heap space then normal.
<application
android:largeHeap="true">
</application>
I will not recommend you the 2nd solution, but it works :)
Hope it will help :)
Related
I use the following code. With large image files it returns null, small ones display. Does anyone know if there is a size limitation with BitmapFactory.decodeFile?
File imgFile = new File(path);
if(imgFile.exists())
{
Bitmap myBitmap=null;
myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath());
ImageView imageView=(ImageView)findViewById(R.id.imageView1);
if(myBitmap!=null) {
imageView.setImageBitmap(myBitmap);
}
else {
Toast.makeText(MainActivity.this, "NULL", Toast.LENGTH_LONG).show();
}
} else Toast.makeText(MainActivity.this, "NO FILE", Toast.LENGTH_LONG).show();
how large is it? how many MBs?
do you get an OutOfMemoryException because each mobile app have specific amount of memory (heap size) and a large bitmap might exceed this limit, so u get a null
EDIT:
unless the big bitmap by chance, is corrupted of some weird format that the app could not decode? try another large bitmap
From experience there is a limit and when you hit that resolution limit for openGL you will see the following type of error message:
Bitmap too large to be uploaded into a texture (4128x2322, max=4096x4096)
You may also get a OutOfMemoryException error like many have noted.
First of all you should do this:
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(inputStream, null, options);
final int width = options.outWidth;
final int height = options.outHeight;
Now that you have the width and height, it's time to make an intelligent decision about how to rescale it.
I would at this point look at the full code example (really only two methods to copy/paste) from Google at Loading Large Bitmaps Efficiently.
I need to display original image in full screen in gallery form. For thumb it will be work perfectly and when I try to display that image in full screen with original source it will not be able to display. In most cases if the image resolution is greater then 2000 then it will display error bitmap too large to be uploaded into a texture android.
I want to prevent this, I have search google but not getting any answer regarding this.
I came across the same problem and came up with a one liner solution for this problem here:
Picasso.with(context).load(new File(path/to/File)).fit().centerCrop().into(imageView);
i just created a if else function to check if the image is bigger than 1M pixels here's the sample code:
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
if (requestCode == SELECT_PICTURE) {
Uri selectedImageUri = data.getData();
selectedImagePath = getPath(selectedImageUri);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
Bitmap bitmap = BitmapFactory.decodeFile(selectedImagePath);
int height = bitmap.getHeight(), width = bitmap.getWidth();
if (height > 1280 && width > 960){
Bitmap imgbitmap = BitmapFactory.decodeFile(selectedImagePath, options);
imageView.setImageBitmap(imgbitmap);
System.out.println("Need to resize");
}else {
imageView.setImageBitmap(bitmap);
System.out.println("WORKS");
}
Google provided a training how to do that. Download the sample from Displaying Bitmaps Efficiently
Take a look to ImageResizer class.
ImageResizer.decodeSampledBitmapFrom* use this method to get downscaled image.
This is the code I used to rectify my problem of fitting an image of size 3120x4196 resolution in an image view of 4096x4096 resolution. Here ImageViewId is the id of the image view created in the main layout and ImageFileLocation is the path of the image which is to be resized.
ImageView imageView=(ImageView)findViewById(R.id.ImageViewId);
Bitmap d=BitmapFactory.decodeFile(ImageFileLcation);
int newHeight = (int) ( d.getHeight() * (512.0 / d.getWidth()) );
Bitmap putImage = Bitmap.createScaledBitmap(d, 512, newHeight, true);
imageView.setImageBitmap(putImage);
You don't need to load the whole image, cause it's too large and probably your phone won't able to show the full bitmap pixels.
You need to scale it first according to your device screen size.
This is the best method that I found and it works pretty good:
Android: Resize a large bitmap file to scaled output file
I found a way to do this without using any external libraries:
if (bitmap.getHeight() > GL10.GL_MAX_TEXTURE_SIZE) {
// this is the case when the bitmap fails to load
float aspect_ratio = ((float)bitmap.getHeight())/((float)bitmap.getWidth());
Bitmap scaledBitmap = Bitmap.createBitmap(bitmap, 0, 0,
(int) ((GL10.GL_MAX_TEXTURE_SIZE*0.9)*aspect_ratio),
(int) (GL10.GL_MAX_TEXTURE_SIZE*0.9));
imageView.setImageBitmap(scaledBitmap);
}
else{
// for bitmaps with dimensions that lie within the limits, load the image normally
if (Build.VERSION.SDK_INT >= 16) {
BitmapDrawable ob = new BitmapDrawable(getResources(), bitmap);
imageView.setBackground(ob);
} else {
imageView.setImageBitmap(bitmap);
}
}
Basically, the maximum image dimensions are a system-imposed limit. The above approach will correctly resize bitmaps that exceed this limit. However, only a portion of the entire image will be loaded. To change the region displayed, you can alter the x and y parameters of the createBitmap() method.
This approach handles bitmaps of any size, including pictures taken with professional cameras.
References:
Android Universal Image Loader.
My app decodes a lot of bitmaps from sd card, so I want to reuse existing bitmaps to decrease GC work. I see examples from Android Training and this video and it works perfect for BitmapFactory.decodeResource, but not for BitmapFactory.decodeFile. I use next code:
private void testBitmapReusing() {
BitmapFactory.Options options = newOptions();
Bitmap bitmap = decode(options);
options.inBitmap = bitmap;
bitmap = decode(options);
}
private BitmapFactory.Options newOptions() {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
options.inMutable = true;
return options;
}
private Bitmap decode(BitmapFactory.Options options) {
return BitmapFactory.decodeFile("/mnt/sdcard/sun.jpg", options);
// return BitmapFactory.decodeResource(getResources(), R.drawable.sun, options);
}
Commented code (BitmapFactory.decodeResource) works as expected, it decodes new bitmap using existing bitmap. But uncommented code (BitmapFactory.decodeFile) doesn't decode new bitmap. It just writes to log message "E/BitmapFactory: Unable to decode stream: java.lang.IllegalArgumentException: Problem decoding into existing bitmap"
So where is my mistake?
UPDATE
I realize my fail. I try to decode GIF images, but it is impossible reuse bitmaps in GIF format. The docs says:
The source content must be in jpeg or png format (whether as a resource or as a stream)
It might not be what you're looking for directly, but the alternate approach would be to create a Map (e.g. a HashMap) and add the resources to that as soon as they're being called. Something like this:
HashMap<String, Bitmap> map = new HashMap<String, Bitmap>();
public Bitmap loadBitmap(String location) {
if (map.containsKey(location))
return map.get(location);
Bitmap bmp = yourDecodeMethod(location);
map.put(location, bmp);
return bmp;
}
This is the same way I use when I do not want to reload resources every time. Sorry if this is not what you wanted to do, I tried my best to understand your actual goal :)
Good luck!
I'm tring to get image from gallery (by intent).
I got this error:
985120-byte external allocation too large for this process.
Out of memory: Heap Size=4871KB, Allocated=2472KB, Bitmap Size=19677KB
VM won't let us allocate 985120 bytes
That's my code where I get image:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
....
mBitmap = Media.getBitmap(this.getContentResolver(), data.getData());
...
}
How can i solve it ?
-------- UPDATE ---------
I noticed that if I select a pre-existent image (HTC photo installed) I get this error. If I select image picked from camera all works fine.
So, I change my code according to this http://developer.android.com/training/displaying-bitmaps/load-bitmap.html:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
InputStream stream = getContentResolver().openInputStream(data.getData());
mBitmap = BitmapFactory.decodeStream(stream,null,options);
stream.close();
But now the bitmap is NULL !!!
It looks like your application uses a lot of high-res bitmap (the bitmap memory partition is 19677KB). The sie of 'heap' and 'allocated' are quite normal, and should not be of any problem. Make sure that you remove unused bitmap from memory. You can free a bitmap from memory by calling bitmap.recycle(), or setting the reference to it to null. Take a look at LruCache if you want to cache bitmaps for performance reasons.
I always wrap decoding in a while loop increasing the inSampleSize and catching OutOfMemoryError. This will give you the maximum possible resolution image. Always use LRU caches!
Bitmap image;
boolean success = false;int counter = 0;
while (success == false && counter < 10)
{
try
{
image = BitmapFactory.decodeFile(photoPath, options);
success = true;
}
catch(OutOfMemoryError e)
{
System.gc();
options.inSampleSize++;
counter++;
}
}
I use the following function to retrieve a contact's photo in Android where the person's lookup key is given:
public Bitmap getContactPhoto(String lookup_key) {
Uri lookUpUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookup_key);
Uri contentUri = ContactsContract.Contacts.lookupContact(ctx.getContentResolver(), lookUpUri);
InputStream stream = null;
try {
stream = ContactsContract.Contacts.openContactPhotoInputStream(ctx.getContentResolver(), contentUri);
}
catch (Exception e) {
}
if (stream != null) {
return BitmapFactory.decodeStream(stream, null, bitmapOptions);
}
else {
return null;
}
}
When I display those contact photos in a list view, I sometimes read the following error in the developer console's crash reports:
Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget(Heap Size=6023KB, Allocated=3002KB, Bitmap Size=27152KB)
Does this mean that my app may use about 3MB of heap but a single bitmap had roughly 27MB?
I read a lot of questions here on Stack Overflow concerning OutOfMemory error but it was mainly about:
leaking contexts so that the garbage collector cannot free the resources
huge bitmaps that need to be scaled
But how can I prevent the error in my case? Since I only get the contacts' photos, I don't know if I have huge bitmaps that must be scaled. And leaking a context doesn't seem to be the case here.
This is how the images are displayed:
imageView.setVisibility(View.VISIBLE);
imageView.setBackgroundResource(R.drawable.background);
if (<BITMAP_OBJECT> != null) {
imageView.setImageBitmap(<BITMAP_OBJECT>);
}
else {
imageView.setImageBitmap(null);
}
When you are setting your Bitmap to an ImageView, try doing this:
Bitmap oldBitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
imageView.setImageDrawable(null);
oldBitmap.recycle();
imageView.setImageBitmap(newBitmap);
It may not complete get rid of the problem, but it certainly makes it happen much more rarely.
Have you tried scaling your bitmap? Here is how you do it...
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile( filename, options );
options.inJustDecodeBounds = false;
options.inSampleSize = 2; // adjust sample size to whatever you want it to be
bitmap = BitmapFactory.decodeFile( filename, options );
if ( bitmap != null && exact ) {
bitmap = Bitmap.createScaledBitmap( bitmap, width, height, false );
}
Here in the method BitmapFactory.decodeFile, I create a scaled Bitmap so that it consumes lesser memory than it did before, it solved my problem, what about yours?