I've got very critical problem.
Only Android 4.1, Bitmap is recycled automatically!
I didn't call recycle() in my code!
My project works fine in other OS versions( ~ 4.0.3) with any resolutions.
Other projects have same problem, too.
All image files are in drawable-nodpi folder.
I resized them to fit for resolution of any devices, always.
public Bitmap GetBitmap(int resource){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDither = true;
options.inPurgeable = true;
Bitmap tmp = null;
try{
tmp = BitmapFactory.decodeResource(mResources, resource, options);
}catch(OutOfMemoryError e){
options.inSampleSize = 2;
tmp = BitmapFactory.decodeResource(mResources, resource, options);
}
return tmp;
}
public Bitmap GetScaledBitmap(int resource, int width, int height, boolean filter){
Bitmap tmp = GetBitmap(resource);
Bitmap img = Bitmap.createScaledBitmap(tmp, width, height, filter);
tmp.recycle();
tmp = null;
return img;
}
In my testing,
Same bitmap instance, but the problem occurs depending on resizing value.
ex)
int width = 100;
Bitmap imgStar = MyResourceManager.getInstance().GetScaledBitmap(R.drawable.star, width, width , true); -> returns recycled instance.
width = 200;
imgStar = MyResourceManager.getInstance().GetScaledBitmap(R.drawable.star, width, width, true); -> returns normal instance.
In different resolutions, imgStar works fine, but the problem occurs in other bitmap instance.
Similarly, When I change resizing value, it works fine.
In same resolution, the problem occurs in other bitmap instance, if I change the name of image files folder.
drawable-nodpi -> drawable -> drawable-ldpi, ..., drawable-xdpi.
Same resizing value, it works fine if I put other resource id.
ex)
int width = 100;
Bitmap imgStar = MyResourceManager.getInstance().GetScaledBitmap(R.drawable.star, width, width , true); -> returns recycled instance.
imgStar = MyResourceManager.getInstance().GetScaledBitmap(R.drawable.diamond, width, width, true); -> returns normal instance.
Please... what can I do?! T ^ T
The reason you're getting different results from different sizes may be because createScaledBitmap will return the original object if it is the same size you are scaling to.
I had the same problem, doing the same thing you are. I was able to fix it this way:
public Bitmap GetScaledBitmap(int resource, int width, int height, boolean filter) {
Bitmap tmp = GetBitmap(resource);
Bitmap img = Bitmap.createScaledBitmap(tmp, width, height, filter);
//copy the image to be sure you are not using the same object as the tmp bitmap
img=img.copy (Bitmap.Config.RGB_565,false);
tmp.recycle();
tmp = null;
return img;
}
Here I copied the bitmap to make sure it wasn't just a refernce to the tmp bitmap object before I recycled the tmp bitmap. Of course you can use any bitmap config you need.
I believe XdebugX was correct in his finding although you don't need to create a copy. Simply check if the memory locations are the same between your resized and original bitmaps.
public Bitmap GetScaledBitmap(int resource, int width, int height, boolean filter){
Bitmap tmp = GetBitmap(resource);
if (tmp == null) {
return null;
}
Bitmap img = Bitmap.createScaledBitmap(tmp, width, height, filter);
/***
* Bitmap#createScaledBitmap will return the original object
* if it is the same size you are scaling to.
*/
if (tmp != img) {
Log.d(TAG, "Full size image recycled");
tmp.recycle();
} else {
Log.w(TAG, "Resized bitmap was the same as the fullsize bitmap");
}
return img;
}
I'd rather suggest to try checking if the bitmap is already recycled before to recycle:
if (!tmp.isRecycled()) tmp.recycle();
Related
i'm developer an app that have more image (about 3000) that has space in filename like:
Im An image.jpg
being new to Android, I only found out now that are not accepted by the resources that have blank spaces or capital letters in the name, and this is a very big problem!
I use the same images in an application for iOS (without problems), and it would be almost impossible now to change all the names.
there is a way to solve this problem without renaming images?
if not, is there a way to quickly rename 3000 images?
thanks at all
there is a way to solve this problem without renaming images?
Nope. Res files can not have spaces.
You could put them in the assets folder and use them fom there, but you will have to get each image as a bitmap without the possibility of using the R.drawable.image_name notation.
I use this method:
public static Bitmap getFoodImage(Context context, String foodType){
InputStream bitmap = null;
Bitmap bit = null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
try {
bitmap = context.getAssets().open(foodType.toUpperCase() + ".png");
if(bitmap != null){
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
int sWidth = metrics.widthPixels;
options.inJustDecodeBounds = true;
Bitmap dBit = BitmapFactory.decodeStream(bitmap, null, options);
int intrinsicHeight = options.outHeight;
int intrinsicWidth = options.outWidth;
//int height = (int) (intrinsicWidth * proportion);
int nHeight = (int) (intrinsicHeight/1.3);
try {
bitmap.reset();
} catch (IOException e) {
Log.e("SGNAM", e.getMessage());
}
options.inJustDecodeBounds = false;
dBit = BitmapFactory.decodeStream(bitmap, null, options);
Bitmap bP = Bitmap.createBitmap(dBit, 0, 0, intrinsicWidth, nHeight);
if(sWidth < intrinsicWidth){
bit = Bitmap.createScaledBitmap(bP, intrinsicWidth, intrinsicHeight, false);
}
else{
bit = Bitmap.createScaledBitmap(bP, sWidth, intrinsicHeight, false);
}
bitmap.close();
dBit = null;
bP = null;
}
} catch (IOException e) {
e.printStackTrace();
}
return bit;
}
As you can see i have all the image names in uppercase but you can skip that part if not needed (same for the png).
If you are in your onCreate method and the image you want is called An image.png (remember that in this example the .png is added by the method):
Context context = this;
Bitmap myBitmap = getFoodImage(context, An image);
You can set this bitmap as a resource for an imageView this way:
ImageView myImageView = findViewById(R.id.imageView);
myImageView.setImageBitmap(myBitmap);
This should do he trick.
My app is an OCR app base on Tesseract. It will do OCR task from camera picture. Users can take many pictures and put them into an OCR queue. To get more accuracy, I want to keep high quality image (I choose min size is 1024 x 768 (maybe larger in future), JPEG, 100% quality). When users take many pictures, there are three things to do:
Save the image data byte[] to file and correct EXIF.
Correct the image orientation base on device's orientation. I know there are some answers that said the image which comes out of the camera is not oriented automatically, have to correct it from file, like here and here. I'm not sure about it, I can setup the camera preview orientation correctly, but the image results aren't correct.
Load bitmap from taken picture, convert it to grayscale and save to another file for OCR task.
And here is my try:
public static boolean saveBitmap(byte[] bitmapData, int orientation, String imagePath, String grayScalePath) throws Exception {
Boolean rotationSuccess = false;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap originalBm = null;
Bitmap bitmapRotate = null;
Bitmap grayScale = null;
FileOutputStream outStream = null;
try {
// save directly from byte[] to file
saveBitmap(bitmapData, imagePath);
// down sample
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, options);
int sampleSize = calculateInSampleSize(options, Config.CONFIG_IMAGE_WIDTH, Config.CONFIG_IMAGE_HEIGHT);
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
originalBm = BitmapFactory.decodeFile(imagePath, options);
Matrix mat = new Matrix();
mat.postRotate(orientation);
bitmapRotate = Bitmap.createBitmap(originalBm, 0, 0, originalBm.getWidth(), originalBm.getHeight(), mat, true);
originalBm.recycle();
originalBm = null;
outStream = new FileOutputStream(new File(imagePath));
bitmapRotate.compress(CompressFormat.JPEG, 100, outStream);
// convert to gray scale
grayScale = UIUtil.convertToGrayscale(bitmapRotate);
saveBitmap(grayScale, grayScalePath);
grayScale.recycle();
grayScale = null;
bitmapRotate.recycle();
bitmapRotate = null;
rotationSuccess = true;
} catch (OutOfMemoryError e) {
e.printStackTrace();
System.gc();
} finally {
if (originalBm != null) {
originalBm.recycle();
originalBm = null;
}
if (bitmapRotate != null) {
bitmapRotate.recycle();
bitmapRotate = null;
}
if (grayScale != null) {
grayScale.recycle();
grayScale = null;
}
if (outStream != null) {
try {
outStream.close();
} catch (IOException e) {
}
outStream = null;
}
}
Log.d(TAG,"save completed");
return rotationSuccess;
}
Save to file directly from byte[]
public static void saveBitmap(byte[] bitmapData, String fileName) throws Exception {
File file = new File(fileName);
FileOutputStream fos;
BufferedOutputStream bos = null;
try {
final int bufferSize = 1024 * 4;
fos = new FileOutputStream(file);
bos = new BufferedOutputStream(fos, bufferSize);
bos.write(bitmapData);
bos.flush();
} catch (Exception ex) {
throw ex;
} finally {
if (bos != null) {
bos.close();
}
}
}
Calculate scale size
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and
// keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
When save complete, this image is loaded into thumbnail image view by UIL. The problem is the save task is very slow (wait some second before save complete and load into view), and sometime I got OutOfMemory exception. Is there any ideas to reduce the save task and avoid OutOfMemory exception?
Any help would be appreciated!
P/S: the first time I try to convert byte[] to bitmap instead of save to file, and then rotate and convert to grayscale, but I still got above issues.
Update: here is the grayscale bitmap process:
public static Bitmap convertToGrayscale(Bitmap bmpOriginal) {
int width, height;
height = bmpOriginal.getHeight();
width = bmpOriginal.getWidth();
Bitmap bmpGrayscale = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bmpGrayscale);
Paint paint = new Paint();
ColorMatrix cm = new ColorMatrix();
cm.setSaturation(0);
ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
paint.setColorFilter(f);
c.drawBitmap(bmpOriginal, 0, 0, paint);
return bmpGrayscale;
}
The OutOfMemory exception seldom occurred (just a few times) and I can't reproduce it now.
Update:
Since you're still saying that the method takes too long time I would define a callback interface
interface BitmapCallback {
onBitmapSaveComplete(Bitmap bitmap, int orientation);
onBitmapRotateAndBWComlete(Bitmap bitmap);
}
Let your activity implement the above interface and convert the byte[] to bitmap in top of your saveBitmap method and fire the callback, before the first call to save. Rotate the imageView based on the orientation parameter and set a black/white filter on the imageView to fool the user into thinking that the bitmap is black and white (do this in your activity). See to that the calls are done on main thread (the calls to imageView). Keep your old method as you have it. (all steps need to be done anyway) Something like:
public static boolean saveBitmap(byte[] bitmapData, int orientation, String imagePath, String grayScalePath, BitmapCallback callback) throws Exception {
Boolean rotationSuccess = false;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap originalBm = null;
Bitmap bitmapRotate = null;
Bitmap grayScale = null;
FileOutputStream outStream = null;
try {
// TODO: convert byte to Bitmap, see to that the image is not larger than your wanted size (1024z768)
callback.onBitmapSaveComplete(bitmap, orientation);
// save directly from byte[] to file
saveBitmap(bitmapData, imagePath);
.
.
// same as old
.
.
saveBitmap(grayScale, grayScalePath);
// conversion done callback with the real fixed bitmap
callback.onBitmapRotateAndBWComlete(grayScale);
grayScale.recycle();
grayScale = null;
bitmapRotate.recycle();
bitmapRotate = null;
rotationSuccess = true;
How do you setup your camera? What might be causing the long execution time in the first saveBitmap call, could be that you are using the default camera picture size settings and not reading the supported camera picture size and choosing best fit for your 1024x768 image needs. You might be taking big mpixel images and saving such, but in the end need you need < 1 mpixles (1024x768). Something like this in code:
Camera camera = Camera.open();
Parameters params = camera.getParameters();
List sizes = params.getSupportedPictureSizes();
// Loop camera sizes and find best match, larger than 1024x768
This is probably where you will save most of the time if you are not doing this already. And do it only once, during some initialization phase.
Increase the buffer to 8k in saveBitmap, change the 1024*4 to 1024*8, this would increase the performance at least, not save any significant time perhaps.
To save/reuse bitmap memory consider using inBitmap field, if you have a post honeycomb version, of BitmapFactory.Options and set that field to point to bitmapRotate bitmap and send options down to your convertToGrayscale method to not need allocating yet another bitmap down in that method. Read about inBitmap here: inBitmap
I am trying to make an application, which fetches the image from the storage and place it into that application image size so that it looks good for it.
Scaling bitmaps from memory can be very memory intensive. To avoid crashing your app on old devices, I recommend doing this.
I use these two methods to load a bitmap and scale it down. I split them into two functions. createLightweightScaledBitmapFromStream() uses options.inSampleSize to perform a rough scaling to the required dimensions. Then, createScaledBitmapFromStream() uses a more memory intensive Bitmap.createScaledBitmap() to finish scaling the image to the desired resolution.
Call createScaledBitmapFromStream() and you should be all set.
Lightweight scaling
public static Bitmap createLightweightScaledBitmapFromStream(InputStream is, int minShrunkWidth, int minShrunkHeight, Bitmap.Config config) {
BufferedInputStream bis = new BufferedInputStream(is, 32 * 1024);
try {
BitmapFactory.Options options = new BitmapFactory.Options();
if (config != null) {
options.inPreferredConfig = config;
}
final BitmapFactory.Options decodeBoundsOptions = new BitmapFactory.Options();
decodeBoundsOptions.inJustDecodeBounds = true;
bis.mark(Integer.MAX_VALUE);
BitmapFactory.decodeStream(bis, null, decodeBoundsOptions);
bis.reset();
final int width = decodeBoundsOptions.outWidth;
final int height = decodeBoundsOptions.outHeight;
Log.v("Original bitmap dimensions: %d x %d", width, height);
int sampleRatio = Math.max(width / minShrunkWidth, height / minShrunkHeight);
if (sampleRatio >= 2) {
options.inSampleSize = sampleRatio;
}
Log.v("Bitmap sample size = %d", options.inSampleSize);
Bitmap ret = BitmapFactory.decodeStream(bis, null, options);
Log.d("Sampled bitmap size = %d X %d", options.outWidth, options.outHeight);
return ret;
} catch (IOException e) {
Log.e("Error resizing bitmap from InputStream.", e);
} finally {
Util.ensureClosed(bis);
}
return null;
}
Final Scaling (Calls lightweight scaling first)
public static Bitmap createScaledBitmapFromStream(InputStream is, int maxWidth, int maxHeight, Bitmap.Config config) {
// Start by grabbing the bitmap from file, sampling down a little first if the image is huge.
Bitmap tempBitmap = createLightweightScaledBitmapFromStream(is, maxWidth, maxHeight, config);
Bitmap outBitmap = tempBitmap;
int width = tempBitmap.getWidth();
int height = tempBitmap.getHeight();
// Find the greatest ration difference, as this is what we will shrink both sides to.
float ratio = calculateBitmapScaleFactor(width, height, maxWidth, maxHeight);
if (ratio < 1.0f) { // Don't blow up small images, only shrink bigger ones.
int newWidth = (int) (ratio * width);
int newHeight = (int) (ratio * height);
Log.v("Scaling image further down to %d x %d", newWidth, newHeight);
outBitmap = Bitmap.createScaledBitmap(tempBitmap, newWidth, newHeight, true);
Log.d("Final bitmap dimensions: %d x %d", outBitmap.getWidth(), outBitmap.getHeight());
tempBitmap.recycle();
}
return outBitmap;
}
I am trying to load some very small images (average size is 90kb) into a gridview in Android. Whenever I load more than 9 images then I am getting memory issues. I have tried scaling the images to a smaller size and although this works to a certain extent it is not really a true solution as the picture quality is awful.
The code is below
private Context mContext;
private ArrayList<Bitmap> photos = new ArrayList<Bitmap>();
public Bitmap [] mThumbIds;
public ImageAdapter(Context c) {
mContext = c;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
public Bitmap scaleBitmap(String imagePath) {
Bitmap resizedBitmap = null;
try {
int inWidth = 0;
int inHeight = 0;
InputStream in;
in = new FileInputStream(imagePath);
// decode image size (decode metadata only, not the whole image)
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(in, null, options);
in.close();
in = null;
// save width and height
inWidth = options.outWidth;
inHeight = options.outHeight;
// decode full image pre-resized
in = new FileInputStream(imagePath);
options = new BitmapFactory.Options();
// calc rought re-size (this is no exact resize)
options.inSampleSize = Math.max(inWidth/300, inHeight/300);
// decode full image
Bitmap roughBitmap = BitmapFactory.decodeStream(in, null, options);
// calc exact destination size
Matrix m = new Matrix();
RectF inRect = new RectF(0, 0, roughBitmap.getWidth(), roughBitmap.getHeight());
RectF outRect = new RectF(0, 0, 300, 300);
m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
float[] values = new float[9];
m.getValues(values);
// resize bitmap
resizedBitmap = Bitmap.createScaledBitmap(roughBitmap, (int) (roughBitmap.getWidth() * values[0]), (int) (roughBitmap.getHeight() * values[4]), true);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return resizedBitmap;
}
public void populateGrid() {
File sdDir = new File("mnt/sdcard/Pictures");
File[] sdDirFiles = sdDir.listFiles();
for(File singleFile : sdDirFiles) {
String filePath = singleFile.getAbsolutePath();
Bitmap bmp = scaleBitmap(filePath);
photos.add(bmp);
}
mThumbIds = photos.toArray(new Bitmap[(photos.size())]);
}
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) { // if it's not recycled, initialize some attributes
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8, 8, 8, 8);
} else {
imageView = (ImageView) convertView;
}
imageView.setImageBitmap(mThumbIds[position]);
return imageView;
}
#Override
public int getCount() {
return mThumbIds.length;
}
}
Two considerations:
The 90kb compressed image size doesn't really matter. The memory use is dictated by the actual resolution of the bitmap -- in this case, 300*300*4bpp, so about 360k per bitmap.
Gingerbread has some flaws because of the combination of the fact that Bitmap memory is stored in a native array (rather than on the Java heap) combined with the fact that garbage collection occurs concurrently. Because of this fact, it sometimes takes the memory manager longer to realize that Bitmap memory can be re-used.
So, the ramifications of this are:
Consider the actual decompressed Bitmap size when estimating memory usage.
Recycle intermediate bitmaps as much as possible, to help get the memory reclaimed faster. For example, if you are scaling a bitmap, save a reference to the pre-scaled source, and recycle the source after scaling is complete (compare to the result of the scaling, since it's possible that the same Bitmap is returned).
If you can, test your code on an ICS device. You can then use the heap inspection tools to get a sense about where the most memory is being used. Sine ICS allocates bitmap memory on the Java heap, you'll have an accurate picture of the bitmap memory usage.
Bitmap use a lot of memory, if you have the image saved into memory already it would probably be better to just use the path of the file and push it on an ImaveView.
ImageView img = new ImageView(getApplicationContext());;
img.setImageBitmap(BitmapFactory.decodeFile(media.getThumbPath()));
img.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
myLinearLayout.addView(img);
Something like this might work better. this way you are not storing all the Bitmaps into your heap.
I've encountered totally bizarre behavior in the way Android loads bitmaps into ImageView. For example, I have a 500x313 image file called urimg_01.jpg. With this code:
img.setImageResource(R.drawable.urimg_01);
Bitmap bitmap = ((BitmapDrawable) img.getDrawable()).getBitmap();
Log.v(TAG,"---------------------> bitmap width = "+bitmap.getWidth());
Log.v(TAG,"---------------------> bitmap height = "+bitmap.getHeight());
the ImageView bitmap is 750x470. (I have a Nexus S with 480x800 display.)
With this code, which reads a copy of the same file located in getFilesDir():
Log.v(TAG,"image file is "+filelist[0].getAbsolutePath());
FileInputStream fis = new FileInputStream(filelist[0]);
FileDescriptor fd = fis.getFD();
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd);
if (bitmap != null) {
Log.v(TAG,"---------------------> bitmap width = "+bitmap.getWidth());
Log.v(TAG,"---------------------> bitmap height = "+bitmap.getHeight());
img.setImageBitmap(bitmap);
fis.close();
}
the ImageView bitmap is 500x313, as expected.
In the case of setImageResource(), where the devil is it getting 750x470 from?? And how do I get it to use the correct dimensions for resources in drawable?
Your resource is being scaled up because it's probably stored in a medium density folder and you're using a high density device. See Bitmap getWidth returns wrong value for details.
here you get your bitmap
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd);
now scale it according to device height and width
Display display=this.getWindowManager().getDefaultDisplay();
int w=display.getWidth();
int h=display.getHeight();
Bitmap newBitmap=Bitmap.createScaledBitmap(oldBitmap, w, h, false);
//now setImage to background with new Bitmap
One important thing "this" should be activity context
:) cheers