Saturday, July 14, 2012

Scale bitmap Efficiently

Remark: I have a exercise "Display Gallery selected image using BitmapFactory" before. The original idea is to select image using Android build-in app Gallery, and display on a ImageView, FOR SMALL IMAGE. Recently found that it cannot display large image, such as original photos from camera ~ but I don't know why no error reported!!!

It may be due to the Bitmap is too large for the ImageView. To scale-down the bitmap before display, Android Developer Site have a good article to describe "Loading Large Bitmaps Efficiently".


The old exercise is modified with scale-down bitmap and re-posted here:

with scale-down bitmap


Main Code:
package com.example.androidselectimage;

import java.io.FileNotFoundException;

import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {
 
 TextView textTargetUri;
 ImageView targetImage;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button buttonLoadImage = (Button)findViewById(R.id.loadimage);
        textTargetUri = (TextView)findViewById(R.id.targeturi);
        targetImage = (ImageView)findViewById(R.id.targetimage);
     
        buttonLoadImage.setOnClickListener(new Button.OnClickListener(){
         
         @Override
         public void onClick(View arg0) {
          // TODO Auto-generated method stub
          Intent intent = new Intent(Intent.ACTION_PICK,
            android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
          startActivityForResult(intent, 0);
         }});
        
    }

 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  // TODO Auto-generated method stub
  super.onActivityResult(requestCode, resultCode, data);
  
  if (resultCode == RESULT_OK){
   Uri targetUri = data.getData();
   textTargetUri.setText(targetUri.toString());
   
   Toast.makeText(getApplicationContext(), 
     "ImageView: " + targetImage.getWidth() + " x " + targetImage.getHeight(), 
     Toast.LENGTH_LONG).show();
   
   Bitmap bitmap;
   bitmap = decodeSampledBitmapFromUri(
     targetUri,
     targetImage.getWidth(), targetImage.getHeight());
      
   if(bitmap == null){
    Toast.makeText(getApplicationContext(), "the image data could not be decoded", Toast.LENGTH_LONG).show();
    
   }else{
    Toast.makeText(getApplicationContext(), 
      "Decoded Bitmap: " + bitmap.getWidth() + " x " + bitmap.getHeight(), 
      Toast.LENGTH_LONG).show();
    targetImage.setImageBitmap(bitmap);
   } 
  }
 }
 
 /*
  *  How to "Loading Large Bitmaps Efficiently"?
  *  Refer: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
  */
 
 public Bitmap decodeSampledBitmapFromUri(Uri uri, int reqWidth, int reqHeight) {

  Bitmap bm = null;
  
  try{
   // First decode with inJustDecodeBounds=true to check dimensions
   final BitmapFactory.Options options = new BitmapFactory.Options();
   options.inJustDecodeBounds = true;
   BitmapFactory.decodeStream(getContentResolver().openInputStream(uri), null, options);

      // Calculate inSampleSize
      options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

      // Decode bitmap with inSampleSize set
      options.inJustDecodeBounds = false;
      bm = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri), null, options);
  } catch (FileNotFoundException e) {
   e.printStackTrace();
   Toast.makeText(getApplicationContext(), e.toString(), Toast.LENGTH_LONG).show();
  }
  
     return bm;
 }
    
 public 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; 
 }
    
}


Layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello_world" />
<Button
    android:id="@+id/loadimage"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Load Image" />
<TextView
    android:id="@+id/targeturi"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />
<ImageView
    android:id="@+id/targetimage"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:scaleType="centerCrop" />
</LinearLayout>


Download the files.

14 comments:

Unknown said...

hello, if you have the 2 images in imageview, as I can then store the bitmap to attach to send to an email?

Unknown said...
This comment has been removed by the author.
Erik said...

Please read Send email with Image by starting activity using Intent of ACTION_SEND.

John said...

I have your code implemented but it does not seem to work for larger files specifically ones I choose from the gallery that were taken by the phones camera. It will display a blank screen (well purplish bg) and the toast will say "decoded bitmap 1 x 1" Any ideas?

AJC said...

Question:How i can perform face detection on images from sd card which are resized already..

Can you tell how i could link this with face detection..Actually what i want to do is when we select an image from gallery that particular image should be forwarded to check for faces and eyes and should toast eye open or closed..All the checking part is working but i dont know how i should integrate that with image in sd card...Will you please help me..I dont know what to do..

Romain said...

Thanks for yout tutorial. However, one question: how to load image when I don't when the size of the ImageView? Thanks

Erik said...

Hello Romain,

This approach just to save memory by scale down the bitmap.

If you don't know the actual ImageView size, you can assign a safe value. For example, if you know the ImageView will be around 200~300, you can use value of 400. Android can adjust it for you, depends on your layout.

Sanul said...

if i load image from drawable of folder in eclipse
what should i change from code?

Erik said...

hello Sanul,

Read Convert a Drawable to bitmap.

Sanul said...

Thank you for your help in advance
I've tried it
but I found java.lang.OutOfMemoryError: bitmap size exceeds VM budget
I tried to adjust the code above but the difference is I display an image from drawable folder
how do I handle that displays a bitmap efficient?

Erik said...

hello Sanul,

It's suggested to reduce your draawable size.

Read Require more memory for App to run if you really need to handle too large drawable.

Sanul said...

thanks for your help
I was greatly helped

Anonymous said...

Top banana, many thanks for this, It's people like you who share code like this that makes the world go round, so much time saved by the efforts of others.

Thank you.

Anonymous said...

I have searched the web endlessly to find the right code for this. Your code here worked instantly! Thanks you so much!