Android: BitmapFactory decode insane slow - android

I intent to program a little gallery application.
So I have a gridview with images, and the images that are displayed are stored
on the local device.
What I got is a Class ImageLoader that loads all images in a specific path in a background thread (AsyncTask) and stores them in a List bitmaps, where ImageItem is a pojo class with an image and a String.
The decoding of Bitmapfactory is very very slow (takes 10mins for 600 images).
How can I improve the following code to speed loading up?
Maybe I need to decode only a scaled instance of the image?
private Bitmap getThumbnail(File f, int THUMBNAIL_SIZE) {
BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
onlyBoundsOptions.inJustDecodeBounds = true;
onlyBoundsOptions.inDither = false; //optional
onlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888; //optional
BitmapFactory.decodeFile(f.getAbsolutePath(), onlyBoundsOptions);
if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) {
return null;
}
int originalSize = (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth) ? onlyBoundsOptions.outHeight :
onlyBoundsOptions.outWidth;
double ratio = (originalSize > THUMBNAIL_SIZE) ? (originalSize / THUMBNAIL_SIZE) : 1.0;
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio);
bitmapOptions.inDither = false;//optional
bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
return BitmapFactory.decodeFile(f.getAbsolutePath(), bitmapOptions);
}
private static int getPowerOfTwoForSampleRatio(double ratio) {
int k = Integer.highestOneBit((int) Math.floor(ratio));
if (k == 0) {
return 1;
} else {
return k;
}
}
I am using a thumbnail size of 300.

this may help you. You shouldn't load all 600 images at once. You should only load the images that are currently within view of the grid view or list view or whatever you are using.
http://www.coderzheaven.com/2013/09/01/faster-loading-images-gridviews-listviews-android-menory-caching-complete-implemenation-sample-code/

Thank you, #BionicSheep for your target-aimed solution.
The sample code on the link brought me to what I was looking for.
However, that code contains some suspect pieces (e.g. it is never written anything into cache). But I finally got it to work smoothly. When I am finished fixing bugs, I'll upload my modified code below.

Related

How can I make my images consume less memory?

I am developing a game and I was working with the graphics, I run into some force closes. So my question is : How can we make images to consume less memory in android?
I´ll explain my game, it´s a logic game with a few small images and a background. I´m testing the app in a galaxy note, 1 GB of RAM, and I thought it could take the high resolution but if I use big image for the background, it force closes after going to the pause layout and back.
So I have lowered the graphics and done with no force closes. Anyway is there some way to avoid this memory issue? I´m setting the images directly on the xml is that wrong?
Solved
I have decided to go with this method, thanks to Durairaj Packirisamy for the answer
Here is my code:
Bitmap unscaledimgswitch = BitmapFactory.decodeResource(getResources(), R.drawable.switch1on);
ImageView switch1 = (ImageView) findViewById(R.id.switch1);
int viewheight = screenheight / 10;
int imgheight = unscaledimgswitch.getHeight();
switch1.getLayoutParams().height = viewheight;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inSampleSize = ScaledFactor.getScaleFactor(imgheight ,viewheight); // de esta forma cargo la imagen del tamaño exacto necesario
options.inJustDecodeBounds = false;
imgswitch = BitmapFactory.decodeResource(getResources(), R.drawable.switch1on, options);
switch1.setImageBitmap(imgswitch);
And in an other class
static int getScaleFactor(int imgheight, int viewheight) {
int result;
result = imgheight / viewheight ;
return result;
}
You need to use a proper BitmapFactory.options.inSampleSize value, If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
Also have a look here:
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

Programmatic Image Resizing in Android, Memory Issues

Days, I've spent working on this. Weeks, perhaps. Literally. :(
So I've got an image on an SD card that more than likely came out of the built-in camera. I want to take that image and downsample it to an arbitrary size (but always smaller and never larger). My code uses standard Android Bitmap methods to decode, resize, recompress, and save the image. Everything works fine as long as the final image is smaller than 3MP or so. If the image is larger, or if I try to do several of these at once, the application crashes with an OutOfMemoryError. I know why that's happening, and I know it's happening for a perfectly legitimate reason, I just want it to not happen anymore.
Look, I'm not trying to launch a rocket here. All I want to do is resize a camera image and dump it to an OutputStream or even a temporary file. Surely someone out there must have done such a thing. I don't need you to write my code for me, and I don't need my hand held. But between my various programming abortions and days of obsessed Googling, I don't even know which direction to head in. Roughly speaking, does anyone know how to decode a JPEG, downsample it, re-compress it in JPEG, and send it out on an OutputStream without allocating a massive amount of memory?
Ok I know it's a little bit late but, I had this problem and I found solution. It is actually easy and I am sure it supports back to api 10(I have no idea about before 10). I tried this with my phone. It is a samsung galaxy s2 with an 8mp camera and the code perfectly resized camera images to the 168x168 as well as images i found on web. I checked the images by using file manager too. I never tried resizing images to bigger resoulation.
private Bitmap resize(Bitmap bp, int witdh, int height){
return Bitmap.createScaledBitmap(bp, width, height, false);
}
you can save it like this
private void saveBitmap(Bitmap bp) throws FileNotFoundException{
String state = Environment.getExternalStorageState();
File folder;
//if there is memory card available code choose that
if (Environment.MEDIA_MOUNTED.equals(state)) {
folder=Environment.getExternalStorageDirectory();
}else{
folder=Environment.getDataDirectory();
}
folder=new File(folder, "/aaaa");
if(!folder.exists()){
folder.mkdir();
}
File file=new File(folder, (int)(Math.random()*10000)+".jpg");
FileOutputStream os=new FileOutputStream(file);
bp.compress(Bitmap.CompressFormat.JPEG, 90, os);
}
thanks to this link
The following code is from my previous project. Key point is "options.inSampleSize".
public static Bitmap makeBitmap(String fn, int minSideLength, int maxNumOfPixels) {
BitmapFactory.Options options;
try {
options = new BitmapFactory.Options();
options.inPurgeable = true;
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fn, options);
if (options.mCancel || options.outWidth == -1
|| options.outHeight == -1) {
return null;
}
options.inSampleSize = computeSampleSize(
options, minSideLength, maxNumOfPixels);
options.inJustDecodeBounds = false;
//Log.e(LOG_TAG, "sample size=" + options.inSampleSize);
options.inDither = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeFile(fn, options);
} catch (OutOfMemoryError ex) {
Log.e(LOG_TAG, "Got oom exception ", ex);
return null;
}
}
private static int computeInitialSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
double w = options.outWidth;
double h = options.outHeight;
int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
(int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
int upperBound = (minSideLength == UNCONSTRAINED) ? 128 :
(int) Math.min(Math.floor(w / minSideLength),
Math.floor(h / minSideLength));
if (upperBound < lowerBound) {
// return the larger one when there is no overlapping zone.
return lowerBound;
}
if ((maxNumOfPixels == UNCONSTRAINED) &&
(minSideLength == UNCONSTRAINED)) {
return 1;
} else if (minSideLength == UNCONSTRAINED) {
return lowerBound;
} else {
return upperBound;
}
}

BitmapFactory.decodeStream out of memory despite using reduced sample size

I have read many related posts concerning memory allocation problems with decoding bitmaps, but am still unable to find the solution to the following problem even after using the code provided in the official website.
Here is my code:
public static Bitmap decodeSampledBitmapFromResource(InputStream inputStream, int reqWidth, int reqHeight) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1) {
baos.write(buffer, 0, len);
}
baos.flush();
InputStream is1 = new ByteArrayInputStream(baos.toByteArray());
InputStream is2 = new ByteArrayInputStream(baos.toByteArray());
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is1, null, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeStream(is2, null, options);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
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) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
bitmap = decodeSampledBitmapFromResource(inputStream, 600, 600);
I am getting "Out of memory error on a 3250016 - byte allocation" in this line:
return BitmapFactory.decodeStream(is2, null, options);
It would seem to me that 3.2 MB is small enough to be allocated. Where am I going wrong? How can I solve this?
EDIT
After looking into this solution HERE by N-Joy it works fine with Required size 300 but my required size is 800, so i am still getting the error.
The method decodeSampledBitmapFromResource is not memory efficient because it uses 3 streams: the ByteArrayOutputStream baos, ByteArrayInputStream is1 and ByteArrayInputStream is2, each of those stores the same stream data of the image (one byte array for each).
And when I test with my device (LG nexus 4) to decode an 2560x1600 image on SDcard to target size 800 it takes something like this:
03-13 15:47:52.557: E/DecodeBitmap(11177): dalvikPss (beginning) = 1780
03-13 15:47:53.157: E/DecodeBitmap(11177): dalvikPss (decoding) = 26393
03-13 15:47:53.548: E/DecodeBitmap(11177): dalvikPss (after all) = 30401 time = 999
We can see: too much memory allocated (28.5 MB) just to decode 4096000 a pixel image.
Solution: we read the InputStream and store the data directly into one byte array and use this byte array for the rest work.
Sample code:
public Bitmap decodeSampledBitmapFromResourceMemOpt(
InputStream inputStream, int reqWidth, int reqHeight) {
byte[] byteArr = new byte[0];
byte[] buffer = new byte[1024];
int len;
int count = 0;
try {
while ((len = inputStream.read(buffer)) > -1) {
if (len != 0) {
if (count + len > byteArr.length) {
byte[] newbuf = new byte[(count + len) * 2];
System.arraycopy(byteArr, 0, newbuf, 0, count);
byteArr = newbuf;
}
System.arraycopy(buffer, 0, byteArr, count, len);
count += len;
}
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(byteArr, 0, count, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
int[] pids = { android.os.Process.myPid() };
MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
Log.e(TAG, "dalvikPss (decoding) = " + myMemInfo.dalvikPss);
return BitmapFactory.decodeByteArray(byteArr, 0, count, options);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
The method that does the calculation:
public void onButtonClicked(View v) {
int[] pids = { android.os.Process.myPid() };
MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
Log.e(TAG, "dalvikPss (beginning) = " + myMemInfo.dalvikPss);
long startTime = System.currentTimeMillis();
FileInputStream inputStream;
String filePath = Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/test2.png";
File file = new File(filePath);
try {
inputStream = new FileInputStream(file);
// mBitmap = decodeSampledBitmapFromResource(inputStream, 800, 800);
mBitmap = decodeSampledBitmapFromResourceMemOpt(inputStream, 800,
800);
ImageView imageView = (ImageView) findViewById(R.id.image);
imageView.setImageBitmap(mBitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
Log.e(TAG, "dalvikPss (after all) = " + myMemInfo.dalvikPss
+ " time = " + (System.currentTimeMillis() - startTime));
}
And the result:
03-13 16:02:20.373: E/DecodeBitmap(13663): dalvikPss (beginning) = 1823
03-13 16:02:20.923: E/DecodeBitmap(13663): dalvikPss (decoding) = 18414
03-13 16:02:21.294: E/DecodeBitmap(13663): dalvikPss (after all) = 18414 time = 917
This is a common issue which user normally faces while playing with large bitmaps and there are lots a questions discussed on site, here, here, here and here and many more, even though user not able to manipulate the exact solution.
I stumbled upon a library sometime back which manages bitmaps smoothly and others links which I listed below. Hope this helps!
smoothie-Library
Android-BitmapCache
Android-Universal-Image-Loader
Solution for OutOfMemoryError: bitmap size exceeds VM budget
ARGB_8888 uses more memory as it takes Alpha color value so my suggestion is to use RGB_565 as stated HERE
Note: Quality will be little low compared to ARGB_8888.
You're probably holding on to previous bitmap references. I'm guessing that you're executing this code several times and never executing bitmap.recycle(). Memory will inevitably run out.
I had many problems with Bitmap memory usage.
Results:
Most devices have limited heap memory for graphics, most small devices are limited to 16MB for overall apps, not just your app
Use 4 bit or 8 bit or 16 bit bitmaps if applicable
Try to draw shapes from scratch, omit bitmaps if possible.
Use WebView to dynamically load as many images as you like, it's built using NDK (low level) so has no GDI heap memory restrictions.
It works smooth and fast :)
Out of memory problems when decoding bitmaps are not often linked with the image size you are decoding.
Of course if you try to open an image 5000x5000px you will fail with a OutOfMemoryError, but with the size of 800x800px it is totally reasonable and should work fine.
If your device is out of memory with a 3.2 MB image it's likely because you are leaking context somewhere in the app.
It's the first part of this post:
I guess problem is not in your layout, problem is somewhere else in
your code. and probably you are leaking context somewhere.
What it means it's that you are using Activity Context in components that should not, preventing them to be garbage collected. Because there components often are held by activities, those activities are not GC and your java heap will grow very fast and your app will crash at one time or another.
As Raghunandan said, you will have to use MAT to find wich Activity/Component is held and remove the context leak.
The best way I found for now to detect context leak is orientation change.
For example, rotate your ActivityMain multiple times, run MAT and check if you have only one instance of ActivityMain. If you have multiple ones (as much as rotation changes) it means there is a context leak.
I found years ago a good tutorial on using MAT. Maybe there is better one now.
Other posts on memory leaks:
Android - memory leak or?
Out of memory error on android emulator, but not on device
Have a look at this video. http://www.youtube.com/watch?v=_CruQY55HOk. Do not use system.gc() as suggested in the video. Use a MAT Analyzer to find out memory leaks. The returned bitmap is too huge causing memory leak i guess.
It seems you have large image to display.
You can download image and save to sdcard (example) then you can user this
code to display image from sdcard.
i also had same problem earlier.. and i have managed it by using this function where you can get scale as your required width and height.
private Bitmap decodeFile(FileInputStream f)
{
try
{
//decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(f,null,o);
//Find the correct scale value. It should be the power of 2.
final int REQUIRED_SIZE=70;
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;
}
//decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize=scale;
return BitmapFactory.decodeStream(f, null, o2);
}
catch (FileNotFoundException e) {}
return null;
}
and refer Memory Leak Error Android and error in loading images to gridview android

Loading multiple high quality bitmaps and scaling them

I've got a serious performance issue in my app, when loading up bitmaps it seems to take up way to much memory.
I have a drawable folder which contains the bitmap sizes for all android devices, these bitmaps are of high quality. Basically it goes though each bitmap and makes a new one for the device depending on the size. (Decided to do it this way because it supports the correct orientation and any device). It works but it's taking up way to much memory and takes along time to load. Can anyone make any suggestions on the following code.
public Bitmap getBitmapSized(String name, int percentage, int screen_dimention, int frames, int rows, Object params)
{
if(name != "null")
{
_tempInt = _context.getResources().getIdentifier(name, "drawable", _context.getPackageName());
_tempBitmap = (BitmapFactory.decodeResource(_context.getResources(), _tempInt, _BM_options_temp));
}
else
{
_tempBitmap = (Bitmap) params;
}
_bmWidth = _tempBitmap.getWidth() / frames;
_bmHeight = _tempBitmap.getHeight() / rows;
_newWidth = (screen_dimention / 100.0f) * percentage;
_newHeight = (_newWidth / _bmWidth) * _bmHeight;
//Round up to closet factor of total frames (Stops juddering within animation)
_newWidth = _newWidth * frames;
//Output the created item
/*
Log.w(name, "Item");
Log.w(Integer.toString((int)_newWidth), "new width");
Log.w(Integer.toString((int)_newHeight), "new height");
*/
//Create new item and recycle bitmap
Bitmap newBitmap = Bitmap.createScaledBitmap(_tempBitmap, (int)_newWidth, (int)_newHeight, false);
_tempBitmap.recycle();
return newBitmap;
}
There's an excellent guide over on the Android Training site:
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
It's about efficient loading of bitmap images - highly recommended!
This will save space. If not using Alpha colors it would be better not to use one with the A channel.
Options options = new BitmapFactory.Options();
options.inScaled = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
// or Bitmap.Config.RGB_565 ;
// or Bitmap.Config.ARGB_4444 ;
Bitmap newBitmap = Bitmap.createScaledBitmap(_tempBitmap, (int)_newWidth, (int)_newHeight, options);

Android -- Map markers displaying differently (dip/px or decompression)

I'm having an odd problem with my map pin sizes. To preserve dynamic-ness, the map pins for different categories are stored on a site's server so that they can be changed at any point even after the app is published.
I'm caching the pins every time I download them and I only ever re-download them if the server sends back a bit saying that one has changed since last I downloaded it. The first time I grab the pins, I use the bitmaps before I save them to files and the map markers are the correct size. Every time after that I'm loading a saved version of the pins straight from the image file. These are displaying considerably smaller than they are when using the bitmaps from the first download.
At first, I thought it was a problem with the way I'm saving the PNGs, but their sizes are correct (64 x 64). Is this a dip/px issue or do I need to decompress the image files with some sort of option?
Here's how I grab the images the first time:
public static Bitmap loadMapPin(String category, int width, int height) {
URL imageUrl;
category = category.toLowerCase().replace(" ", "");
try {
imageUrl = new URL(PIN_URL+category+".png");
InputStream is = (InputStream) imageUrl.getContent();
Options options = new Options();
options.inJustDecodeBounds = true; //Only find the dimensions
//Decode without downloading to find dimensions
BitmapFactory.decodeStream(is, null, options);
boolean scaleByHeight = Math.abs(options.outHeight - height) >= Math.abs(options.outWidth - width);
if(options.outHeight * options.outWidth >= width * height){
// Load, scaling to smallest power of 2 that'll get it <= desired dimensions
double sampleSize = scaleByHeight
? options.outHeight / height
: options.outWidth / width;
options.inSampleSize =
(int)Math.pow(2d, Math.floor(
Math.log(sampleSize)/Math.log(2d)));
}
options.inJustDecodeBounds = false; //Download image this time
is.close();
is = (InputStream) imageUrl.getContent();
Bitmap img = BitmapFactory.decodeStream(is, null, options);
return img;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
And here's how I'm loading them from the cached file:
BitmapFactory.decodeFile(filepath);
Thanks in advance!
I've found that, by default, decompressing an image to a bitmap doesn't scale with high density screens. You have to set the density to none. In other words, you specify that the image is meant for an unknown density.
Solution:
Bitmap b = BitmapFactory.decodeFile(filepath);
b.setDensity(Bitmap.DENSITY_NONE);

Categories

Resources