How to create the bitmap and decode it at the same time - android

I am creating a bitmap and it takes about 11 mb in the heap , though it is of the small size. Well I wanted to know if I can create the bitmap and also sclae it as a same time. The reason I want to do it is , the memory allocation , as If I understand correctly from different bitmap questions which are posted here , and that is
The bitmap allocates the memory as when it is created
So if its , then scaling it again take some process time and also increase the heap size until and unless the garbage collection is not occurred
So what I am doing is
screenHeight = displaymetrics.heightPixels;
screenWidth = displaymetrics.widthPixels;
float aspectRatio = screenWidth / screenHeight;
int modifiedScreenHeight = 400;
int modifiedScreenWidth = (int) (modifiedScreenHeight * aspectRatio);
mBitmap = Bitmap.createBitmap(modifiedScreenWidth, modifiedScreenHeight, Bitmap.Config.ARGB_8888);
So now it is creating the bitmap and allocation the memory , by memory analyzer tool in android studio I can see that it took 11mb in memory.
But I want to minimize them ,I have visited a link and I want to do some more scaling by options as show in this video . but it uses the file to decode such as
BitmapFactory.decodeFile(??,options);
where as I have no file to decode from , I want to decode it from the bitmap I created and to wash away the last created bitmap to clear the memory.
Or if it is possible to set the options when creating it so that we can avoid from extra memory allocation .
Please help.

You can use this using BitmapFactory.Options - specifically, use the options to decode the width / height of the bitmap, then sampleSize to determine how large the generated bitmap will be.
According to your example, you'd like the width/height of the bitmap to be 400 by 400 * aspectRatio. So, first, you'll need to see how large the bitmap needs to be. Do this as so:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(??, options);
int bitmapWidth = options.outWidth;
int bitmapHeight = options.outHeight;
This action will only decode the bitmaps size, without actually allocating memory for the bitmap's pixels. This is good because it's a very quick and light operation which doesn't require much resources and helps you make a more educated decision when loading the bitmap. Now we must use these size to determine how big the generated bitmap will be.
int sampleSize = 1;
while (bitmapWidth / sampleSize > 400 && bitmapHieght / sampleSize > 400 * aspectRatio)
sampleSize *= 2;
sampleSize must be a power of 2 for this to work, and what it will do is determine how many pixels to "skip" when reading the bitmap into memory. This algorithm will set a sample size to a size equal to 1st sample size which will produce a bitmap immediately smaller than the required bounds. You can tweak this if you'd like a slightly different implementation.
Now that you have the sample size, set it with in the options object and load the actual bitmap:
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
Bitmap bitmap = BitmapFactory.decodeFile(??, options);
The generated bitmap will be smaller than the required bounds, thus limiting your memory requirements for creating the bitmap object.
Hope this helps.

Related

Is this a correct way to get a bitmap's size before decoding it into memory?

I know I can use these ways to get the Bitmap's size:
bitmap.getAllocationByteCount(); //API 19
bitmap.getByteCount(); //API 12
bitmap.getRowBytes() * bitmap.getHeight(); //earlier version
But This all need a Bitmap object which mean I need to decode the bitmap into memory before, this may case OOM Exception. So I use this way to get the size before I can get Bitmap object:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(bitmapPath, options);
int picWidth = options.outWidth;
int picHeight = options.outHeight;
int size = 4 * picHeight * picWidth; //Byte
I think it cost 32 bits per pixel because Android decode bitmap use RGB_8888 default.
Is this a correct way or is there a better way to do this ?
You can select the bitmap configuration with BitmapFactory.Options.inPreferredConfig. This will allow you to specify a configuration where you know for sure how many bytes per pixel will be occupied by the Bitmap. I believe RGB_8888 is the default.
You can probably not 100% reliably prevent an OOM on a bitmap decode since you don't have a guarantee for a set amount of contiguous free space in memory for the allocation of the Bitmap. But you can certainly adjust your sample size and config to reduce the load.
Just create a File object with the path to the Bitmap. Then get file.length() to get the file size.
e.g. File bitmap = new File(pathToBitmap);
bitmap.length();
You'll have to put a try/catch clause as required.

Android bitmap size reduction not reducing memory

I'm new to android and am working on my first real app so i apologize if this question has been answered, or isn't detailed enough. I'm having the common OutOfMemoryError when loading bitmaps to my android app. I've searched around on stackoverflow and went through the google tutorial that describes how to use InSampleSize to reduce the memory for each bitmap.
When testing out the app to see how much memory I was saving by down scaling my bitmaps I noticed that the heap size was still growing at the same rate. I.E. When I use an InSampleSize = 8 vs not scaling the bitmap at all (or InSampleSize = 1) the heap grows the same for each.
I have tried printing the byteCount with:
myImage.getByteCount()
for both of the scaling SampleSizes listed above and they both have identical ByteCounts. Is this the expected behavior? Shouldn't the byte count be reduced since I'm downscaling images?
I was expecting to see the memory used to the bitmap reduced by a factor of 8 or so. Am I missing something, or is my understanding of image scaling incorrect? Thanks.
edit: After doing some testing I discovered that if I used createScaledBitmap the memory was reduced but it required me first inflating the original Bitmap then scaling. I'm assuming this is non-ideal since it requires that. I thought the first method would do this for me but according to my heap dumps it isn't.
Initial Code and heap dump:
private static Bitmap decodeSampledBitmap(Resources res, int resId, int width, int height
{
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inPreferredConfig = Config.ARGB_8888;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, width, height);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
Updated code (same as above with the only difference being) and heap dump:
private static Bitmap decodeSampledBitmap(Resource res, int ResId, int width, int height)
{
....
Bitmap bitmap = BitmapFactory.decodeResource(res, resId, options);
return Bitmap.createScaledBitmap(bitmap, options.outWidth/options.inSampleSize, options.outHeight/options.inSampleSize, false);
}
It won't let me post images due to my reputation but according to heap dump it is using 79.155MB. Using the second technique the heap dump is using 26.912 MB.
Are you scaling the Bitmap after decoding it using InSampleSize?

Get Image size(int bytes) before decoding android

I've an image (3648x2736) around 4.19 MB(size in disk) and I wanted to load it in my application but it crashed as it should because of not enough memory. So to avoid these kind of crashes I put a validator before decoding the images(No, I do not want to use inSampleSize to make it smaller).
long maxMemory = Runtime.getRuntime().maxMemory();
long nativeUsage = Debug.getNativeHeapAllocatedSize();
long heapSize = Runtime.getRuntime().totalMemory();
long heapRemaining = Runtime.getRuntime().freeMemory();
long memoryLeft = maxMemory - (heapSize - heapRemaining) - nativeUsage;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
int bitmapSize = options.outHeight * options.outWidth * 4;
if (bitmapSize < memoryLeft)
return BitmapFactory.decodeFile(filePath);
Now one thing I want to make sure is, am I calculating bitmapSize properly ? Because the image file size is only 4.19 MB and memoryLeft was more than 8 MB, yet app crashed. That means it's storing every pixel as 4 bytes(PNGs), right ? then shouldn't it be 3 bytes for jpeg ? or is there something else I need to know ?
Since Bitmap is just a set of uncompressed pixels no matters what format it is - png or jpeg or else. Only factor you should remember is Bitmap.Config which describe color scheme for bitmap. For example Config.RGB_565 will take 2 bytes per pixel (5 bit red, 6 bit green, 5 bit green channel) and Config.ARGB_8888 will take 4 bytes per pixel (8 bits per each channel).
You can set Bitmap.Config while decoding image using BitmapFactory.Options.inPreferredConfig but as I understood from BitmapFactory.Options docs this is not guaranteed.

java.lang.OutOfMemory with large high-resolution images

I'm developing an Android app which uses multiple large images in several of its activities. Each image is around 1280x800 and I load about 2-4 of these images for each Activity. I realize that these images are very large in terms of the memory that is allocated to each individual app on a device, but how can I display them at their original resolution without running into a java.lang.OutOfMemory error? I need these images to be displayed at their full size on a screen (scaling is done automatically by xml when the screen is smaller than the image). I saw several solutions involving shrinking down images into thumbnails and storing those into memory, but wouldn't that cause the image to lose it's original size/resolution? Thanks for your help!
There are a few things you can do about this.
The first thing that comes to mind is that 1280x800 is (probably) your entire screen, so you should only need to display one at a time. Don't hold the others in memory while you do that.
Still a 1280x800 image at 4 bytes per pixel is only 4MB, and tablets seem to all offer 48MB of heap these days. You should be able to hold a few in memory if you need to. If you're running out of memory, it's possible you're leaking. If you watch in DDMS, does your memory usage continue to grow as you change activities?
A common source of leaks is the bitmaps themselves. Be sure to call Bitmap#recycle when you're done with them.
If it really comes down to it, and you cannot fit into the heap space provided, you can also try adding android:largeHeap="true" to the application tag in your manifest. This will request the system offer you more heap space - up to 256MB on some devices. This should be a last resort, though, as it will vary by device, and be completely ignored on some (the original Kindle Fire comes to mind).
You can see just how much total heap space you have with Runtime.getRuntime().maxMemory();. See this answer for a more detailed explanation. It's trickier to see just how much you're using, but there is a description here if you want to tackle that beast.
Finally, there may be a better way to load your images than specifying them in xml. Be sure to read this developer guide page. Even if you must leave them in xml, I have seen marked improvement in memory usage by splitting the image assets into the drawable-hdpi, drawable-mdpi, etc. directories rather than just dumping them in drawable.
This article describes pretty well how to create a heap dump and analyze it using Eclipse MAT. This will help you find the most likely suspects for memory leaks pretty quickly.
Again I point you to this great link I found from another SO Question that has tutorials of how to properly over come the problem.
Image needs to scaled before loading the image using BitmapFactory or related methods.
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) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
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);
}
whole thing is explained in Android developer site, Loading Large Bitmaps Efficiently

Android load big images from external storage

I need to load lots of big images (500 .png files) from the SD card to my app. Do I always have to convert images to Bitmap and make Bitmap files? I don't want to resize the Heap.
Is there another way to read the images from SD card?
If you're displaying them in a view, then you have to load them into memory in their entirety.
You didn't mention how large your images will get, but what we do in our photo gallery is to keep a list of SoftReferences to these bitmaps, so that the garbage collector can throw them away when they're not visible (i.e. when the view displaying them gets discarded--make sure that this actually happens, e.g. by using AdapterView). Combine this with lazy loading of these bitmaps and you should be good.
The internal representation of the image in your app is a collection of bits and bytes - not an image of any specific format (png, bmp, etc).
The image is converted to this internal representation when the image is loaded by the BitmapFactory.
It is usually not a good idea to load all the bitmaps at once, you will quickly run out of memory...
If your image's dimension is very big, you must to resize them before loading in to ImageView. Otherwise, even one picture can easily cause out of memory problem. I don't know how many images you want to display concurrently and how big they are. But I suggest you to resize them before displaying them.
To resize image and show it, you can use this code:
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(fileInputStream, null, bitmapOptions);
//reduce the image size
int imageWidth = bitmapOptions.outWidth;
int imageHeight = bitmapOptions.outHeight;
int scale = 1;
while (imageWidth/scale >= screenWidth && imageHeight/scale >= screenHeight) {
imageWidth = imageWidth / 2;
imageHeight = imageHeight / 2;
scale = scale * 2;
}
//decode the image with necessary size
fileInputStream = new FileInputStream(cacheFile);
bitmapOptions.inSampleSize = scale;
bitmapOptions.inJustDecodeBounds = false;
imageBitmap = BitmapFactory.decodeStream(fileInputStream, null, bitmapOptions);
ImageView imageView = (ImageView)this.findViewById(R.id.preview);
imageView.setImageBitmap(imageBitmap);
In my android project, I am using this piece of code to resize my HD wallpaper to review it.
Android Save And Load Downloading File Locally

Categories

Resources