I am creating an application with frame by frame animation using almost 150 image frames. The animation is played by changing the background image of an Image view using Handler. Inside the handler I am retrieving the images from the folder /mnt/sdcard/Android/data/ApplicationPackage and changing it dynamically as the image view background by the following way :
FileInputStream in;
in = new FileInputStream(mFrames.get(imgpos));
bitmap = BitmapFactory.decodeStream(in);
if (in != null)
{
in.close();
}
This creates some issues in decoding the file input stream since it takes a lot of time for some images to create the bitmap. Image file size is almost less than 40 KB for each images, but it takes different duration to decode the images from the external directory for the same size files. I have tried to sample the file size and load but it directly affects the image clarity. Can any one please suggest me what is the better way to load the images to a bitmap from the external folder and with the same duration for all the images?
Thanks, Tim
I think you must preload the bitmap before displaying them... Use an array of bitmap, and a background task that load all the images when entering your activity.
Your handler is absolutely not allowed to perform time-consuming tasks such as loading bitmaps !
You can downscale the images before creating the bitmap for them.
public Bitmap decodeSampledBitmapFromPath(String path)
{
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, requiredWidth, requiredHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap newBmap = BitmapFactory.decodeFile(path, options);
return newBmap;
}
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
{
int inSampleSize = 1;
if (imageHeight > reqHeight || imageWidth > reqWidth)
{
if (imageWidth > imageHeight)
{
inSampleSize = Math.round((float)imageHeight / (float)reqHeight);
}
else
{
inSampleSize = Math.round((float)imageWidth / (float)reqWidth);
}
}
return inSampleSize;
}
If the images are a lot bigger than the view in which they are loaded, then this down scaling really helps create the bitmap faster. Also, the scaled Bitmap takes much less memory than directly creating the bitmap.
inJustDecodeBounds in BitmapFactory.Options when set to true lets you get the height and width before creating the Bitmap for it. So you can check if the images are larger than required. If yes, then scale them to the required height and width, and then create its Bitmap.
Hope this helps!
Related
I am new to Android programming. I was trying to display an image from my device's sd card onto an ImageView, using BitmapFactory.decodeFile(imagePath). I can read the image fine from sd card, followed the official documentation to loading bitmaps efficiently. Although one thing I cannot understand is why the bitmap returned is much greater in size than the original size? Original image size was 84.32 kb, the bitmap that I got was of the size 4.096 mb. I was trying to load the bitmap with the height and width of the image, i.e. 1280x800. If I reduce the width and height of the image in the method below , I get reduced size. But why I can't load the image in bitmap with its original size and actual width and height?
Method decodeSampledBitmap():
public static Bitmap decodeSampledBitmap(String path,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
//BitmapFactory.decodeResource(res, resId, options);
BitmapFactory.decodeFile(path, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
//return BitmapFactory.decodeResource(res, resId, options);
return BitmapFactory.decodeFile(path, options);
}
The size of the image on disk has little to do with the size of the image in memory. PNG, JPEG, and GIF images are compressed on disk. In memory, they are uncompressed.
I am trying to load images into my listview from a custom adapter but the Bitmaps do not display the images until I scroll down and then scroll back up and I don't know why! At first when I had loaded the images , the application crashed as I wasn't loading the Images efficiently.
(I am storing images in a DB by encoded to base64. I know it's not a great thing to do but I need to just get things done as quick as possible).
Bonus question What is the best way to trigger an event to happen after TWO asynchronous DB calls are complete?
My code to load Bitmap:
public Bitmap decodeBitmap(String image, int reqWidth, int reqHeight)
{
byte[] decodedByte = Base64.decode(image, 0);
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(decodedByte, 0, decodedByte.length, options);
options.inSampleSize = calculateSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap bmp = BitmapFactory.decodeByteArray(decodedByte, 0, decodedByte.length, options);
return bmp;
}
public int calculateSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
{
final int height = options.outHeight;
final int width = options.outWidth;
int size = 1;
if (height > reqHeight || width > reqWidth)
{
if (width > height)
{
size = Math.round((float) height / (float) reqHeight);
}
else
{
size = Math.round((float) width / (float) reqWidth);
}
}
return size;
}
The best and easiest way to load images into imageviews is to use a background process to accomplish the task, because blocking the interface will be bad user experience. The are a lot of code samples online, for instance you can use the ImageLoader class from this tutorial. Just add it to your project and use it like:
ImageLoader loader = new ImageLoader(context);
loader.DisplayImage(url, imagview);
EDIT:
When you fetch it from the web, you are most probably getting the image as a stream. You could do that, but the most efficient and popular way to deal with images in databases, is to not store the actual image data in the database, because read/write operation are not efficient.
Instead what you could do is, to store the stream you get from the web to a file in some folder, and once you store it, store the path pointing to it in the database where the image should be located. In this way your database is much lighter and you open the image just when you need it. Actually this is the correct way to deal with images because once your image size increase, reading from it won't be possible, since Android limits the cursor window size to 1MB.
So I advice you the modify your code as explained.
I'm working on an app that uses large images (1390 × 870 : 150kb - 50kb). I'm adding images as I tap a trigger/ImageView.
At a certain point I'm getting an out of memory error:
java.lang.OutOfMemoryError
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:613)
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:378)
To resize the image I'm doing this:
Bitmap productIndex = null;
final String imageLoc = IMAGE_LOCATION;
InputStream imageStream;
try {
imageStream = new FileInputStream(imageLoc);
productIndex = decodeSampledBitmapFromResource(getResources(), imageLoc, 400, 400);
productIV.setImageBitmap(productIndex);
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, String resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(resId, options);
}
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 / 3;
final int halfWidth = width / 3;
// 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;
}
I got this way of resizing to save space from the Android Docs:
Loading Large Bitmaps Efficiently
According to the log this like is the culprit in the decodeSampledBitmapFromResource method :
return BitmapFactory.decodeFile(resId, options);
----- edit -----
Here is how I'm adding each item to the FrameLayout.
for(int ps=0;ps<productSplit.size();ps++){
//split each product by the equals sign
List<String> productItem = Arrays.asList(productSplit.get(ps).split("="));
String tempCarID = productItem.get(0);
tempCarID = tempCarID.replace(" ", "");
if(String.valueOf(carID).equals(tempCarID)){
ImageView productIV = new ImageView(Configurator.this);
LayoutParams productParams = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
productIV.setId(Integer.parseInt(partIdsList.get(x)));
productIV.setLayoutParams(productParams);
final String imageLoc = productItem.get(2);
InputStream imageStream;
try {
imageStream = new FileInputStream(imageLoc);
productIndex = decodeSampledBitmapFromResource(getResources(), imageLoc, 400, 400);
productIV.setImageBitmap(productIndex);
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
productLayers.addView(productIV);
}
}
You can use another bitmap-config to heavily decrease the size of the images. The default is RGB-config ARGB8888 which means four 8-bit channels are used (red, green, blue, alhpa). Alpha is transparency of the bitmap. This occupy a lot of memory - imagesize X 4. So if the imagesize is 4 megapixel 16 megabytes will immidiately be allocated on the heap - quickly exhausting the memory.
Instead - use RGB_565 which to some extent deteriorate the quality - but to compensate this you can dither the images.
So - to your method decodeSampledBitmapFromResource - add the following snippets:
options.inPreferredConfig = Config.RGB_565;
options.inDither = true;
In your code:
public static Bitmap decodeSampledBitmapFromResource(Resources res, String resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
options.inPreferredConfig = Config.RGB_565;
options.inDither = true;
return BitmapFactory.decodeFile(resId, options);
}
References:
http://developer.android.com/reference/android/graphics/Bitmap.Config.html#ARGB_8888
High resolution devices such as S4 usually run out of memory if you do not have your image in the proper folder which is drawable-xxhdpi. You can also put your image into drawable-nodpi. The reason it would run out of memorey if your image just in drawable that the android would scale the image thinking that the image was designed for low resolution.
You can use this beautiful library https://github.com/davemorrissey/subsampling-scale-image-view
Here is how I'm adding each item to the FrameLayout
that's the problem, the code keep adding and adding more images, and doesn't matter how well you resize or how much memory the device have, at certain point it WILL run out of memory. That's because every image you add it's keeping in memory.
For this type of situation what the apps do is to use a ViewGroup that can recycle views. I don't know your layout, but usually is a ListView, GridView or a ViewPager, by recycling views you re-use the layout and can dispose re-load images as necessary.
For the specific purpose of loading and resizing images I strongly advise use Picasso library as it is VERY well written, simple to use and stable.
You are still going to need to manage the bitmap memory as I wouldn't try to allocate a total space more than 3x the size of the screen (if you think about it makes sense for scrolling behavior). If you are overlaying one image on top of another, at some point, you're hitting an Out Of Memory error. You may need to look at capturing the prior screen image as a single background image to make sure you still fit within the available memory. Or when a new image overlaps an existing image only load and render the visible portion. If performance becomes an issue, then you may need to consider OpenGL Textures but the memory allocation problem is still the same.
Do go through all of the Displaying Bitmaps Training as it should give you some additional ideas of how to handle display.
Use Fresco library to load large images will avoid this error.
in xml layout
<com.facebook.drawee.view.SimpleDraweeView
android:id="#+id/my_image_view"
android:layout_width="1300dp"
android:layout_height="1300dp"
fresco:placeholderImage="#drawable/my_drawable"
/>
and in javacode
Uri uri = Uri.parse("https://image.png");
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);
I am using bitmaps. When the code runs it shows an out of memory error. How can the error be avoided. My code follows. Thanks in advance.
Bitmap myBitmap = Image.decodeSampledBitmapFromUri(path, 250, 500);
img_cook[index].setImageBitmap(myBitmap);
public static Bitmap decodeSampledBitmapFromUr(String path, int reqWidth,
int reqHeight) {
Bitmap bm = null;
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
bm = BitmapFactory.decodeFile(path, options);
return bm;
}
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
When you have done with your Bitmap, means when your Bitmap done its work then make it recyle and null like below:
bitmap.recycle();
bitmap=null;
OR
I think you are downloading Image from url, so I am suggesting you to use Android Query for this, you will never get this error if you used it.
You can download the jar file from here :
http://code.google.com/p/android-query/downloads/list
Download the jar file and set jar to your Build Path.
AQuery androidAQuery=new AQuery(this);
As an example to load image directly from url:
androidAQuery.id(YOUR IMAGEVIEW).image(YOUR IMAGE TO LOAD, true, true, getDeviceWidth(), ANY DEFAULT IMAGE YOU WANT TO SHOW);
As an example to get Bitmap from url:
androidAQuery.ajax(YOUR IMAGE URL,Bitmap.class,0,new AjaxCallback<Bitmap>(){
#Override
public void callback(String url, Bitmap object, AjaxStatus status) {
super.callback(url, object, status);
//You will get Bitmap from object.
}
});
It's very fast and accurate, and using this you can find many more features like Animation when loading; getting a bitmap, if needed; etc.
Still now your image size are big that why use width and height like that and after set the image the clear the chache
Bitmap myBitmap = Image.decodeSampledBitmapFromUri(path, 60, 60);
img_cook[index].setImageBitmap(myBitmap);
if (myBitmap != null)
{
bitmap.recycle();
bitmap = null;
System.gc();
}
Im guessing you're not getting the OOM exception after you create your first bitmap, but rather, this happens after you load several bitmaps into memory?
Try to improve your efficiency by manually calling recycle() on bitmaps you no longer need. While the GC collects some data of Bitmaps which have all their references freed, the actual memory of the image is stored in native memory, and so a call to bitmap.recycle() is required to release this memory when you need it to be released.
Hope this helps.
Android applications have very low amount of memory. You should manage it carefully to avoid out of memory exception. You can see the
Google's Solution
This question already has answers here:
Android: BitmapFactory.decodeStream() out of memory with a 400KB file with 2MB free heap
(8 answers)
Closed 9 years ago.
I'm having an OutOfMemoryError in my VSD220 (It's a 22" Android based All in one)
for (ImageView img : listImages) {
System.gc();
Bitmap myBitmap = BitmapFactory.decodeFile(path);
img.setImageBitmap(myBitmap);
img.setOnClickListener(this);
}
I really don't know what to do, because this image is below the maximum resolution. The image size is something about (1000x1000), and the display it's 1920x1080.
Any help?
(That foreach cycle is for about 20 elements, it gots broken after 6, or 7 loops..)
Thanks a lot.
Ezequiel.
You should take a look at the training docs for Managing Bitmap Memory. Depending on your OS version, you could use different techniques to allow you to manage more Bitmaps, but you'll probably have to change your code anyway.
In particular, you're probably going to have to use an amended version of the code in "Load a Scaled Down Version into Memory", but I at least have found this section to be particularly useful:
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;
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
This method makes it easy to load a bitmap of arbitrarily large size
into an ImageView that displays a 100x100 pixel thumbnail, as shown in
the following example code:
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
Are you really sure you want to load the same Bitmap 20 times? Don't you want to load it once and set it inside the loop.
Still, loading a 1000x1000 pixel image is not guaranteed to work, regardless of screen resolution. Remember that a 1000x1000 pixel image takes up 1000x1000x4 bytes =~4MB (if you load it as ARGB_8888). If your heap memory is fragmented/too small you may not have enough space to load the bitmap. You may want to look into the BitmapFactory.Options class and experiment with inPreferredConfig and inSampleSize
I would suggest that you either use the suggestion by DigCamara and decide on a size and load a downsampled image of nearly that size (I say nearly because you won't get the exact size using that technique) or that you try to load the full size image and then recursively increase the sample size (by factors of two for best result) until you either reach a max sample size or the image is loaded:
/**
* Load a bitmap from a stream using a specific pixel configuration. If the image is too
* large (ie causes an OutOfMemoryError situation) the method will iteratively try to
* increase sample size up to a defined maximum sample size. The sample size will be doubled
* each try since this it is recommended that the sample size should be a factor of two
*/
public Bitmap getAsBitmap(InputStream in, BitmapFactory.Config config, int maxDownsampling) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
options.inPreferredConfig = config;
Bitmap bitmap = null;
// repeatedly try to the load the bitmap until successful or until max downsampling has been reached
while(bitmap == null && options.inSampleSize <= maxDownsampling) {
try {
bitmap = BitmapFactory.decodeStream(in, null, options);
if(bitmap == null) {
// not sure if there's a point in continuing, might be better to exit early
options.inSampleSize *= 2;
}
}
catch(Exception e) {
// exit early if we catch an exception, for instance an IOException
break;
}
catch(OutOfMemoryError error) {
// double the sample size, thus reducing the memory needed by 50%
options.inSampleSize *= 2;
}
}
return bitmap;
}