Monday, August 26, 2013

Load scaled bitmap

Last post provide UN-RECOMMENDED methods to handle error of 'OutOfMemoryError' and 'Bitmap too large to be uploaded into a texture' for images/bitmaps. Here is another approach, scale-down bitmap before processing and display. It's recommanded by Google's document Loading Large Bitmaps Efficiently for displaying bitmap.

Load scaled bitmap


Please note that the final value of BitmapFactory.Options.inSampleSize used by decoder will be based on powers of 2, any other value will be rounded down to the nearest power of 2.

For example: if the original photos is in 4256x2832, and REQ_WIDTH/REQ_HEIGHT are 1024, the calculated inSampleSize will be 3. But the rounded down value used by decoder will be 2. And the scaled bitmap will be 2128x1416.

Refer to the exercise "Merge images with PorterDuffXfermode", modify MainActivity.java to implement loadScaledBitmap() and calculateInSampleSize() methods.

package com.test.androidimageprocessing;

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.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.example.androidimageprocessing.R;

public class MainActivity extends Activity {

 Button btnLoadImage1, btnLoadImage2;
 TextView textSource1, textSource2;
 Button btnProcessing;
 ImageView imageResult;
 Spinner spinnerMode;

 final int RQS_IMAGE1 = 1;
 final int RQS_IMAGE2 = 2;

 Uri source1, source2;

 String[] arrayModeName = { "ADD", "CLEAR", "DARKEN", "DST", "DST_ATOP",
   "DST_IN", "DST_OUT", "DST_OVER", "LIGHTEN", "MULTIPLY", "OVERLAY",
   "SCREEN", "SRC", "SRC_ATOP", "SRC_IN", "SRC_OUT", "SRC_OVER", "XOR" };

 /*
  * To use Mode.ADD and Mode.OVERLAY, android:minSdkVersion have to be set
  * "11" or higher.
  */
 PorterDuff.Mode[] arrayMode = { Mode.ADD, Mode.CLEAR, Mode.DARKEN,
   Mode.DST, Mode.DST_ATOP, Mode.DST_IN, Mode.DST_OUT, Mode.DST_OVER,
   Mode.LIGHTEN, Mode.MULTIPLY, Mode.OVERLAY, Mode.SCREEN, Mode.SRC,
   Mode.SRC_ATOP, Mode.SRC_IN, Mode.SRC_OUT, Mode.SRC_OVER, Mode.XOR };

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  setContentView(R.layout.activity_main);
  btnLoadImage1 = (Button) findViewById(R.id.loadimage1);
  btnLoadImage2 = (Button) findViewById(R.id.loadimage2);
  textSource1 = (TextView) findViewById(R.id.sourceuri1);
  textSource2 = (TextView) findViewById(R.id.sourceuri2);
  btnProcessing = (Button) findViewById(R.id.processing);
  imageResult = (ImageView) findViewById(R.id.result);

  spinnerMode = (Spinner) findViewById(R.id.mode);
  ArrayAdapter<String> adapter = new ArrayAdapter<String>(
    MainActivity.this, android.R.layout.simple_spinner_item,
    arrayModeName);
  adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
  spinnerMode.setAdapter(adapter);

  btnLoadImage1.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View arg0) {
    Intent intent = new Intent(
      Intent.ACTION_PICK,
      android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    startActivityForResult(intent, RQS_IMAGE1);
   }
  });

  btnLoadImage2.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View arg0) {
    Intent intent = new Intent(
      Intent.ACTION_PICK,
      android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    startActivityForResult(intent, RQS_IMAGE2);
   }
  });

  btnProcessing.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View v) {

    if (source1 != null && source2 != null) {
     Bitmap processedBitmap = ProcessingBitmap();
     if (processedBitmap != null) {
      imageResult.setImageBitmap(processedBitmap);
      Toast.makeText(getApplicationContext(), "Done",
        Toast.LENGTH_LONG).show();
     } else {
      Toast.makeText(getApplicationContext(),
        "Something wrong in processing!",
        Toast.LENGTH_LONG).show();
     }
    } else {
     Toast.makeText(getApplicationContext(),
       "Select both image!", Toast.LENGTH_LONG).show();
    }

   }
  });
 }

 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  if (resultCode == RESULT_OK) {
   switch (requestCode) {
   case RQS_IMAGE1:
    source1 = data.getData();
    textSource1.setText(source1.toString());
    break;
   case RQS_IMAGE2:
    source2 = data.getData();
    textSource2.setText(source2.toString());
    break;
   }
  }
 }

 private Bitmap ProcessingBitmap() {
  Bitmap bm1 = null;
  Bitmap bm2 = null;
  Bitmap newBitmap = null;

  try {
   /*
    * bm1 = BitmapFactory.decodeStream(getContentResolver()
    * .openInputStream(source1)); bm2 =
    * BitmapFactory.decodeStream(getContentResolver()
    * .openInputStream(source2));
    */

   bm1 = loadScaledBitmap(source1);
   Toast.makeText(getApplicationContext(),
     "bm1: " + bm1.getWidth() + " x " + bm1.getHeight(),
     Toast.LENGTH_LONG).show();

   bm2 = loadScaledBitmap(source2);
   Toast.makeText(getApplicationContext(),
     "bm2: " + bm2.getWidth() + " x " + bm2.getHeight(),
     Toast.LENGTH_LONG).show();

   int w;
   if (bm1.getWidth() >= bm2.getWidth()) {
    w = bm1.getWidth();
   } else {
    w = bm2.getWidth();
   }

   int h;
   if (bm1.getHeight() >= bm2.getHeight()) {
    h = bm1.getHeight();
   } else {
    h = bm2.getHeight();
   }

   Config config = bm1.getConfig();
   if (config == null) {
    config = Bitmap.Config.ARGB_8888;
   }

   newBitmap = Bitmap.createBitmap(w, h, config);
   Canvas newCanvas = new Canvas(newBitmap);

   newCanvas.drawBitmap(bm1, 0, 0, null);

   Paint paint = new Paint();

   int selectedPos = spinnerMode.getSelectedItemPosition();
   PorterDuff.Mode selectedMode = arrayMode[selectedPos];

   paint.setXfermode(new PorterDuffXfermode(selectedMode));
   newCanvas.drawBitmap(bm2, 0, 0, paint);

  } catch (FileNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }

  return newBitmap;
 }

 private Bitmap loadScaledBitmap(Uri src) throws FileNotFoundException {

  // required max width/height
  final int REQ_WIDTH = 800;
  final int REQ_HEIGHT = 800;

  Bitmap bm = null;

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

  // Calculate inSampleSize
  options.inSampleSize = calculateInSampleSize(options, REQ_WIDTH,
    REQ_HEIGHT);

  // Decode bitmap with inSampleSize set
  options.inJustDecodeBounds = false;
  bm = BitmapFactory.decodeStream(
    getContentResolver().openInputStream(src), null, options);

  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) {

   // Calculate ratios of height and width to requested height and
   // width
   final int heightRatio = Math.round((float) height
     / (float) reqHeight);
   final int widthRatio = Math.round((float) width / (float) reqWidth);

   // Choose the smallest ratio as inSampleSize value, this will
   // guarantee
   // a final image with both dimensions larger than or equal to the
   // requested height and width.
   inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
  }

  Toast.makeText(getApplicationContext(),
    "inSampleSize: " + inSampleSize, Toast.LENGTH_LONG).show();

  return inSampleSize;
 }

}


download filesDownload the files.

Next: Fine tune scaled down bitmap to exact size
Related: Scale bitmap with inDither and inPreferQualityOverSpeed


more: Something about processing images in Android

No comments: