Exploring Android BitmapFactory

BitmapFactory is a class in Android that allows you to create Bitmap objects from various sources, such as files, streams, resources, and byte arrays. In this tutorial we will take a look on what are the most often used methods of this class and which scenarios call to use one method over another.

The BitmapFactory class has several decode* static methods, that differ only in the input source. The most often used are:
Bitmap decodeResource(Resources res, int id)
Bitmap decodeResource(Resources res, int id, BitmapFactory.Options opts)

Bitmap decodeFile(String pathName)
Bitmap decodeFile(String pathName, BitmapFactory.Options opts)

Bitmap decodeByteArray(byte[] data, int offset, int length)
Bitmap decodeByteArray(byte[] data, int offset, int length, BitmapFactory.Options opts)

Bitmap decodeStream(InputStream is)
Bitmap decodeStream(InputStream is, Rect outPadding, BitmapFactory.Options opts)
Each of the above methods comes in two variants: one that takes as parameter a BitmapFactory.Options, and one that does not. The BitmapFactory.Options let you specify decoding options. One common scenario when is needed to specify decoding options, (that we will illustrate later), is when loading a scaled down version of the bitmap in order to prevent OutOfMemoryError.

But for the moment lets walk though the above mentioned methods and see where it is appropriate to use them.

1. BitmapFactory.decodeResource, can be used when the image needed to display is located in drawable resource folders.
Bitmap bitmap = BitmapFactory.decodeResource(
                       getResources(), R.drawable.image);
imageView.setImageBitmap(bitmap);
The method accepts the Resources instance and the drawable resource id.

2. BitmapFactory.decodeFile, useful when the image source can be represented as a File object, for example when needed to load an image from the sd card.
File file = new File(Environment
               .getExternalStorageDirectory() + "/Pictures/image.jpg");
Bitmap bitmap = BitmapFactory.decodeFile(file);
imageView.setImageBitmap(bitmap);

3. BitmapFactory.decodeStream is a universal method for loading bitmaps, as any readable source of bytes can be read as an input stream.
In the example below an image is downloaded from the internet and the response is got as InputStream which is converted into a Bitmap.
URLConnection connection = 
            new URL("http://i.imgur.com/9gbQ7YR.jpg").openConnection();
InputStream response = connection.getInputStream();

Bitmap bitmap = BitmapFactory.decodeStream(response)
imageView.setImageBitmap(bitmap);

4. BitmapFactory.decodeByteArray, used to create a bitmap from a byte array. There may be times when the image to be displayed will be available to you as a byte array. One common scenario is when you have the base 64 image representation as a String. In this case, the base 64 string is converted to a byte array and passed to BitmapFactory.decodeByteArray.
String base64String = "...";
byte[] imageData = Base64.decode(base64String, Base64.DEFAULT);

Bitmap bitmap = BitmapFactory
               .decodeByteArray(imageData, 0, imageData.length);
imageView.setImageBitmap(bitmap);

Specifying decoding options to prevent OutOfMemoryError

Working with images in Android is probably one of the few areas but most often encountered, that "remembers" you that the memory available to your application is limited. Usually, the solution to this issue is to subsample the image, or with other words, to load a smaller version into memory. For example, it is not worth loading a 1024 x 500 pixel image, only to be displayed in a 120 x 90 thumbnail image.
And this is the moment where the BitmapFactory.Options enters the scene.

Two of the public fields of Options class are of interest to us in order to subsample the image:

public boolean inJustDecodeBounds - if is set to true, the decoder will return null (no bitmap), but the out fields of the Options instance will be set, allowing the caller to query the bitmap without having to allocate the memory for its pixels.

This "... query the bitmap without having to allocate the memory", is the most important part you should remember in regards to inJustDecodeBounds field.

public int inSampleSize - if set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory.

For example inSampleSize = 4 will scale a 1024 x 500 pixel image, to 256 x 125 pixel image, which is way more acceptable in terms of memory consumption than the original image.

Example:
Bitmap bitmap = scaleBitmapFromResource(R.drawable.image, 250, 120);
imageView.setImageBimtap(bitmap);
public Bitmap scaleBitmapFromResource(int resId, int reqWidth, int reqHeight){
   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);
}
public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Original 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;
}

Niciun comentariu:

Trimiteți un comentariu