Strange out of memory issue while loading an image to a Bitmap object
To fix the OutOfMemory error, you should do something like this:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);
This inSampleSize
option reduces memory consumption.
Here's a complete method. First it reads image size without decoding the content itself. Then it finds the best inSampleSize
value, it should be a power of 2, and finally the image is decoded.
// Decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
try {
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f), null, o);
// The new size we want to scale to
final int REQUIRED_SIZE=70;
// Find the correct scale value. It should be the power of 2.
int scale = 1;
while(o.outWidth / scale / 2 >= REQUIRED_SIZE &&
o.outHeight / scale / 2 >= REQUIRED_SIZE) {
scale *= 2;
}
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e) {}
return null;
}
The Android Training class, "Displaying Bitmaps Efficiently", offers some great information for understanding and dealing with the exception java.lang.OutOfMemoryError: bitmap size exceeds VM budget
when loading Bitmaps.
Read Bitmap Dimensions and Type
The BitmapFactory
class provides several decoding methods (decodeByteArray()
, decodeFile()
, decodeResource()
, etc.) for creating a Bitmap
from various sources. Choose the most appropriate decode method based on your image data source. These methods attempt to allocate memory for the constructed bitmap and therefore can easily result in an OutOfMemory
exception. Each type of decode method has additional signatures that let you specify decoding options via the BitmapFactory.Options
class. Setting the inJustDecodeBounds
property to true
while decoding avoids memory allocation, returning null
for the bitmap object but setting outWidth
, outHeight
and outMimeType
. This technique allows you to read the dimensions and type of the image data prior to construction (and memory allocation) of the bitmap.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
To avoid java.lang.OutOfMemory
exceptions, check the dimensions of a bitmap before decoding it, unless you absolutely trust the source to provide you with predictably sized image data that comfortably fits within the available memory.
Load a scaled down version into Memory
Now that the image dimensions are known, they can be used to decide if the full image should be loaded into memory or if a subsampled version should be loaded instead. Here are some factors to consider:
- Estimated memory usage of loading the full image in memory.
- The amount of memory you are willing to commit to loading this image given any other memory requirements of your application.
- Dimensions of the target ImageView or UI component that the image is to be loaded into.
- Screen size and density of the current device.
For example, it’s not worth loading a 1024x768 pixel image into memory if it will eventually be displayed in a 128x96 pixel thumbnail in an ImageView
.
To tell the decoder to subsample the image, loading a smaller version into memory, set inSampleSize
to true
in your BitmapFactory.Options
object. For example, an image with resolution 2048x1536 that is decoded with an inSampleSize
of 4 produces a bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full image (assuming a bitmap configuration of ARGB_8888
). Here’s a method to calculate a sample size value that is a power of two based on a target width and height:
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;
}
Note: A power of two value is calculated because the decoder uses a final value by rounding down to the nearest power of two, as per the
inSampleSize
documentation.
To use this method, first decode with inJustDecodeBounds
set to true
, pass the options through and then decode again using the new inSampleSize
value and inJustDecodeBounds
set to false
:
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));
You can follow a similar process to decode bitmaps from other sources, by substituting the appropriate BitmapFactory.decode*
method as needed.
I've made a small improvement to Fedor's code. It basically does the same, but without the (in my opinion) ugly while loop and it always results in a power of two. Kudos to Fedor for making the original solution, I was stuck until I found his, and then I was able to make this one :)
private Bitmap decodeFile(File f){
Bitmap b = null;
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(f);
BitmapFactory.decodeStream(fis, null, o);
fis.close();
int scale = 1;
if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE /
(double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
}
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
fis = new FileInputStream(f);
b = BitmapFactory.decodeStream(fis, null, o2);
fis.close();
return b;
}