Hello!
The use of imagery in our Apps is practically mandatory. After all, the Man said "make it look great".
Embedding icons and images as resources in an App is trivial and the
Resources class makes loading them even easier.
But have you tried lately loading a large image file, let's say like an 8Mp picture? Well if you haven't, try it and you'll see that there's not enough memory for that. And don't ever embed a picture that large as a resource in your app.
So how can we load something that we can't load because there's not enough memory? Very carefully and in steps.
1) Loading bitmaps
The Android
Bitmap class possesses several factory methods to create bitmaps but unfortunately it does not solve our problems - and I understand is also not recommended. What is left to us is the super awesome
BitmapFactory class.
To create a bitmap object using BitmapFactory just call one of its several
decode* factory methods, for instance:
...
Bitmap testBitmap=BitmapFactoryClass.decodeResource(resources, R.drawable.myimage);
// do stuff with testBitmap
// recycle testBitmap
...
this creates a Bitmap from the resource
myimage and stores in testBitmap. We can also load from a file on, let's say, the SD card
...
Bitmap testBitmap=BitmapFactoryClass.decodeFile("/mnt/sdcard/picture.jpg";
// do stuff with testBitmap
// recycle testBitmap
...
if we do that to load a 3000x4000 pixels large picture, it will blowup in our faces. It will probably run out of memory loading an image half this size.
To get around this problem, we need to sample the image during the decoding phase.
2) Sampling a large bitmap
the code snippet below does all the hard work of sampling an image and returning it as a
Bitmap object.
A few notes before you use it:
BitmapFactoriy.Options is the secret weapon, more specifically the field
inJustDecodeBounds. Setting it to true will tell the framework to not try loading the image, instead just return its size (returned in
options.outHeight and
options.outWidth). Once we have the dimensions of the image, we can read it scaled down by a factor defined in the field
inSampleSize.
The SDK recomends using inSampleSize in powers of 2 so the line
int sampleSize=(int) Math.pow(2, Math.floor(Math.sqrt(factor)));
tries to find the best power of 2 close to the sample rate you requested. It's simplistic but works fine for almost all cases. My suggestion is you find what's best for your app. I recommend sampling the image to a bigger size than what you really want and then use
Bitmap.createScaledBitmap to set the image to the size you want.
BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeFile(fileName, options);
long totalImagePixes=options.outHeight*options.outWidth;
totalScreenPixels=__some_maximum_number_of_pixels_supported;
if(totalImagePixes>totalScreenPixels){
double factor=(float)totalImagePixes/(float)(totalScreenPixels);
int sampleSize=(int) Math.pow(2, Math.floor(Math.sqrt(factor)));
options.inJustDecodeBounds=false;
options.inSampleSize=sampleSize;
return BitmapFactory.decodeFile(fileName, options);
}
return BitmapFactory.decodeFile(fileName);
Also, not this
the variable sets the maximum number of pixels (i.e, total bytes used) that we want the sampled image to have.
You can use an approach like that or just find a sample size based on your needs. Again, it's simplistic but works majority of cases.
To recap, all you need is to set
inJustDecodeBounds to true, call
decodeFile, set
inSampleSize to the desired value (power of 2 preferably) and call
decodeFile again.
Have fun!