About my app: Taking photos, saving them to the phone memory, using BitmapFactory.decodeFile, loading that bitmap into one fixed size, not changing, image view.
Now, i know this was asked a hundred times already. I've been all over google\android's manage memory guide , but for my task it's kind of an overkill to manage and recycle different bitmaps, since i'm only using one.
Here's my code so far:
..
Bitmap bm_thumb = null;
..
private void setPic() {
/* There isn't enough memory to open up more than a couple camera photos */
/* So pre-scale the target bitmap into which the file is decoded */
/* Get the size of the ImageView */
int targetW = imv_thumb.getWidth();
int targetH = imv_thumb.getHeight();
/* Get the size of the image */
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(PhotoPath, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
bmOptions.inSampleSize = 1;
/* Figure out which way needs to be reduced less */
int scaleFactor = 1;
if ((targetW > 0) || (targetH > 0)) {
scaleFactor = Math.min(photoW/targetW, photoH/targetH);
}
/* Set bitmap options to scale the image decode target */
bmOptions.inSampleSize = scaleFactor; //was after decode bounds
bmOptions.inJustDecodeBounds = false;
/* Decode the JPEG file into a Bitmap */
bm_thumb = BitmapFactory.decodeFile(PhotoPath, bmOptions);
/* Associate the Bitmap to the ImageView */
imv_thumb.setImageBitmap(bm_thumb);
imv_thumb.setVisibility(View.VISIBLE);
}
These lines are not in the code, as they currently confuse me.
BitmapFactory.decodeFile(PhotoPath, bmOptions);
mCurrentBitmap = Bitmap.createBitmap(bmOptions.outWidth, bmOptions.outHeight, Bitmap.Config.ARGB_8888)
bmOptions.inBitmap = bm_thumb;
Do I need to run decodeFile everytime I change my bitMapOptions?
Plus, If the decodeFile methods returns a bitMap, why do i need to also create one?
who am i supposed to give to inBitmap? i've tried both options, non seem to solve my OOM error.
My OOM started quite a long time after first installing and testing my app. My question is, how do I know I have solved the issue? If written correctly, will it fix its error, or do I need to select "clear cache" on the phone, and hope it will never happen again?
Sorry for the long post, I was really trying to remain focused..
Related
Here is my problem: I'm asking my user to choose between different sizes of a same image in order to upload it. My interface shows 4 buttons with each giving informations about the width, the height of the image, and the length of the file.
I managed to produce the four versions of the image calling Bitmap.createScaledBitmap, then compressing those bitmaps to files again.
I'm well aware I need to recycle the bitmap and delete the files when I'm done.
However, when trying to compute a big file, the method createScaledBitmap throws an out of memory exception.
I was told to use the option inJustDecodeBounds to decode the file but it would return a null bitmap.
Does someone have an idea on how to solve this problem?
Here is the code:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bmp = getScaledBitmap(BitmapFactory.decodeFile(uri, options), ratio);
private Bitmap getScaledBitmap(Bitmap bmp, float ratio) {
int srcWidth = bmp.getWidth();
int srcHeight = bmp.getHeight();
int dstWidth = (int)(srcWidth * ratio);
int dstHeight = (int)(srcHeight * ratio);
Bitmap result = Bitmap.createScaledBitmap(bmp, dstWidth, dstHeight, true);
bmp.recycle();
return result;
}
To answer your replies, I don't consider it to be a duplicate. I previously found the thread you linked but it's quite old and it didn't solve my problem.
Anyway, I solved the problem.
I first tried using Glide library which is said to be better for memory management. Though, there was an improvement but it wasn't perfect and the problem occured again.
Using android:largeHeap="true" put an end to my nightmare. This has to be set in the application field of your manifest.
I hope this will help someone else.
I want to make an app, in which I want to take a picture and input x,y axis it will show me corresponding point pixel value from the captured picture .
I don't have any knowledge about image processing well . So, how can I do it ? help me .
The way I have done this in the past is by writing the image to the filesystem and then decoding it. Admittedly its a little inefficient but it's functional.
This requires a number of steps. I suggest you read over this documentation. It describes the process of taking an image and saving it to the file system which you will need to do before you can do the rest of these steps.
Next you will need to use BitmapFactory to decode the image which will give you an instance of Bitmap which you can do all sorts of things with (documented here). In this example I return the Color object for the pixel located at (50, 50) using getPixel(). This example assumes that the path the the image is mCurrentPhotoPath but should be wherever you saved the image.
private Color getColor() {
// Get the dimensions of the View
int targetW = mImageView.getWidth();
int targetH = mImageView.getHeight();
// Get the dimensions of the bitmap
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
// Determine how much to scale down the image
int scaleFactor = Math.min(photoW/targetW, photoH/targetH);
// Decode the image file into a Bitmap sized to fill the View
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
return bitmap.getPixel(50, 50);
}
You can get RGB values from this Color object which is what I think you are wanting to do. That can be found under the Color object documentation on Android Developers as well.
In app I'm working on you can choose between multiple backgrounds and every time when I select background for layout I'm setting it with
relativeLayout.setBackground(getResources().getDrawable(R.drawable.background));
Each of the backgrounds have resolution of 1440x2560 pixels, but they only have around 12 KB each and bit depth 4.
Problem is that sometimes when I select background app crashes with error java.lang.OutOfMemoryError.
Why is this happening and how to fix it?
Problem is quite simple. You need to recycle the bitmap everytime you change it by clicking the button, or else it will stay in memory and eventually throw the out of memory exception.
Anyways, if you want to fix this, keep a reference to your bitmap on your activity.
private Bitmap currentBitmap;
public void changeBitmap(Bitmap bitmap){
relativeLayout.setBackgroundImage(bitmap);
if(currentBitmap != null){
/*this is where the magic happens, you
recycle your old bitmap whose reference you had stored in the global
variable*/
currentBitmap.recycle();
}
currentBitmap = bitmap;
}
Resize the image before loading
public Bitmap resizeBitmap(int targetW, int targetH) {
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(photoPath, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
int scaleFactor = 1;
if ((targetW > 0) || (targetH > 0)) {
scaleFactor = Math.min(photoW/targetW, photoH/targetH);
}
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;
return BitmapFactory.decodeFile(photoPath, bmOptions);
}
The quckfix is android:largeHeap="true" in the AndroidManifest in <application>
But I think it needs a lot of RAM to show the Drawable on the Screen. The Dirty Fix is, when you call the garbage collector programmatically.
I am still searching for the clean fix.
I am using this tutorial as a guideline for adding a functionality to my app:
http://developer.android.com/training/camera/photobasics.html
The example coming with the tutorial is incomplete containing some "mistakes". I put the word mistakes in quotes because the main tutorial's purpouse is covered: work with the camera.
I am focusing on obtain a thumbnail photo from the big one taken. When you run the example you rapidly notice that most of the time the thumbnail for the big photo is not displayed although it is correctly stored in the denoted dir.
Doing a bit of work I discovered the following "mistakes":
1.- The image's path value is frequently lost because the activity is destroyed due to a lack of memory. I fixed that storing the path to the photo in the method onSaveInstanceState().
This way I was always able to access my image but it still did not appear. I continued making some tests and discovered:
2.- Most of the times, when asking for the imageview's measures (width and high) for rescaling the image the values were 0. I thought this could be the problem and found it was caused because you cannot obtain the measures until the view was drawn. So I fixed that with a handler and sending a delayed message (1.5'') to be executed. Now, the measures are always obtained correctly but even though the thumbnail is not displayed most of the times
So I thought the Bitmap.decodeFile method was returning a null value dispite all the variables were setting correctly. But it is not, it is returning a bitmap. So guys and girls, I recognize I am not able to find why the thumbnail is not displayed.
A bit of help would be very appreciated. Thanks!
This is the method for rescaling the image:
//Scaling the real size photo to the image view size
private void setImagenPequena()
{
Log.w("PAth: ", n_path_foto_actual);
// Get the dimensions of the View
int targetW = n_iv_foto.getMeasuredWidth();
int targetH = n_iv_foto.getMeasuredHeight();
// Get the dimensions of the bitmap
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(n_path_foto_actual, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
Log.w("setImagenPequena: ", "photoW: " + Integer.toString(photoW));
Log.w("setImagenPequena: ", "photoH: " + Integer.toString(photoH));
Log.w("setImagenPequena: ", "targetW: " + Integer.toString(targetW));
Log.w("setImagenPequena: ", "targetH: " + Integer.toString(targetW));
if(targetW > 0 && targetH > 0)
{
// Determine how much to scale down the image
int scaleFactor = Math.min(photoW/targetW, photoH/targetH);
bmOptions.inSampleSize = scaleFactor;
}
// Decode the image file into a Bitmap sized to fill the View
bmOptions.inJustDecodeBounds = false;
bmOptions.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeFile(n_path_foto_actual, bmOptions);
if(bitmap == null)
Log.w("valor bitmap: ", "null");
else
Log.w("valor bitmap: ", "!=null");
n_iv_foto.setImageBitmap(bitmap);
}
I have got many issues as well with this tutorial, but I finally fixed it.
what I did :
Change
int scaleFactor = Math.min(photoW/targetW, photoH/targetH);
by
int scaleFactor = Math.max(photoW/targetW, photoH/targetH);
was the key thing. Before that i got blank image instead of the picture.
I put a default picture in my view. It may not be an all-around answer but I thought it gives a better user experience anyway.
You can use http://blog-emildesign.rhcloud.com/?p=590 to get a working decodeSampledBitmapFromFile example.
Otherwise use some of my code here :
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
mWidth = size.x;
mHeight = size.y;
...
private void setPic() {
int targetW = mWidth;
int targetH = mHeight;
// Get the dimensions of the bitmap
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
// Determine how much to scale down the image
int scaleFactor = Math.max(photoW/targetW, photoH/targetH);
bmOptions.inPreferredConfig = Bitmap.Config.RGB_565;
// Decode the image file into a Bitmap sized to fill the View
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath,
bmOptions);
mImageView.setImageBitmap(bitmap);
}
I am very new in android,and trying to put SDcard images in grid view by using Bitmap and BitmapFactory.
But it cause the Error like:
ERROR/AndroidRuntime(6137): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
ERROR/AndroidRuntime(6137): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
ERROR/AndroidRuntime(6137): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:459)
ERROR/AndroidRuntime(6137): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:271)
Well ok, apparently this question will get answered the 100th time on SO. Here's the idea:
first off depending on the Android version on the device:
if you're using Android version 2.x and below (any version prior to Honeycomb) the memory taken by your Bitmap instances will NOT be reflected in the amount of free memory you have on the heap as reported by Runtime.getRuntime().xxxMemory() methods. Those instances are placed in memory OUTSIDE the heap. If you want to track down how much memory a Bitmap instance will use you have to manually calculate it as imageWidth*imageHeight*4. This will give you the bytes taken by your image in (off heap) memory. This memory consumption, as well as the on-heap memory consumption must have a total which is below the max memory allocated by Android to your application on a certain device, if not you'll get an OutOfMemory error.
the total memory allocated to a process by Android depends greatly on the device and Android version. This can be anywhere between 16 and 48 Megs. On older devices is 16 or 32. On newer ones is 32 or 48. This you have to individually check on each device you want to target. You can do it also at runtime, and use it as a guide as to how much stuff you can put in memory (maybe you want to downsample the images before loading them on a device that allocates 16 Mb of memory to your app)
On Android Honeycomb (version 3.x.x) and beyond, a Bitmap instance will use on-heap memory. This makes it easier to track down how much free memory you still have after loading images. Also with these versions of Android, the Bitmap instance will be garbage collected (when possible) automatically. On pre-Honeycomb you have to manyally call
Bitmap.recycle();
to free up the memory taken by your bitmap instance.
When using BitmapFactory to decode images (and create Bitmap instances) you can pass-in options such as to only get the width and height of an image, or to downsample it before decoding. This can help you asses how much memory an image will take BEFORE you actually place it in memory. Check out the docs: http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html
If you feel adventorous you can trick the memory limitation all together, though this doesn't work on all devices: Create an OpenglSurfaceView, and display your images as textures on quads. You can use an orthogonal projection for simplicity (if you only need to give appearance of 2d). Trick here is that you can load an Image in memory as a bitmap, asign it to an OpenGL texture and the clear out that Bitmap instance. The actual image will still be displayeble from the Texture object, and these objects are not limited by the per-process memory limitation.
Do not copy the image in full quality into your app first. Use the Options class to sample down the quality/size a bit:
ContentResolver cr = getContentResolver();
InputStream is = cr.openInputStream(chosenImageUri);
Options optionSample = new BitmapFactory.Options();
optionSample.inSampleSize = 4; // Or 8 for smaller image
Bitmap bitmap = BitmapFactory.decodeStream(is, null, optionSample);
// Bitmap bitmap = BitmapFactory.decodeFile(filePathString, optionSample);
Try using inSampleSize = 8 if you are creating thumbnail bitmaps.
If you find yourself creating several Bitmap objects, each making some changes to the same image, try using bitmap.recycle(). But recycle() can lead to some runtime errors if your app has some reference to the old bitmaps (can be hard to detect), so be careful using it.
Let me know if it helps.
Android is more concerned about memory and the BitmapFactory accepts limited size images only.
I think the following will help you to scale image before use.
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
public class ImageScale
{
/**
* Decodes the path of the image to Bitmap Image.
* #param imagePath : path of the image.
* #return Bitmap image.
*/
public Bitmap decodeImage(String imagePath)
{
Bitmap bitmap=null;
try
{
File file=new File(imagePath);
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(file),null,o);
final int REQUIRED_SIZE=200;
int width_tmp=o.outWidth, height_tmp=o.outHeight;
int scale=1;
while(true)
{
if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
break;
width_tmp/=2;
height_tmp/=2;
scale*=2;
}
BitmapFactory.Options options=new BitmapFactory.Options();
options.inSampleSize=scale;
bitmap=BitmapFactory.decodeStream(new FileInputStream(file), null, options);
}
catch(Exception e)
{
bitmap = null;
}
return bitmap;
}
/**
* Resizes the given Bitmap to Given size.
* #param bm : Bitmap to resize.
* #param newHeight : Height to resize.
* #param newWidth : Width to resize.
* #return Resized Bitmap.
*/
public Bitmap getResizedBitmap(Bitmap bm, int newHeight, int newWidth)
{
Bitmap resizedBitmap = null;
try
{
if(bm!=null)
{
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// create a matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(scaleWidth, scaleHeight);
// recreate the new Bitmap
resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
}
}
catch(Exception e)
{
resizedBitmap = null;
}
return resizedBitmap;
}
}
To get image path from URI use this function :
private String decodePath(Uri data)
{
Cursor cursor = getContentResolver().query(data, null, null, null, null);
cursor.moveToFirst();
int idx = cursor.getColumnIndex(ImageColumns.DATA);
String fileSrc = cursor.getString(idx);
return fileSrc;
}