May 31, 2012

Image Processing with open JPG file dialog

In the former articles in my "Image processing" series, the source image is preset. With the example of "JPG File Chooser", a dialog to load jpg file is added. User can select image to be processed, by touching on MENU and Load jpg.

User can select jpg to process


Please notice that it is a heavy memory needed processing, may be you will get OutOfMemoryError!

Modify from the former article "Unsharp Mask (USM) on Android Image Processing".

Create /res/layout/load_dialog.xml to define the layout of the Load JPG dialog.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/customdialog"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:padding="20dp"
    android:minWidth="300dp">
    <LinearLayout 
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp">
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_launcher"/>
        <Button
            android:id="@+id/up"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="Parent folder"/>
    </LinearLayout>

 <TextView
     android:id="@+id/folder"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <ListView
        android:id="@+id/dialoglist"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

</LinearLayout>


Main code, AndroidImageProcessingActivity.java.
package com.AndroidImageProcessing;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.app.Dialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.MimeTypeMap;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;

public class AndroidImageProcessingActivity extends Activity {
 
 //for Load JPG dialog
 Button buttonUp;
 static final int LOAD_DIALOG_ID = 0;
 TextView textFolder;
 ListView dialog_ListView;
 private List<String> fileList = new ArrayList<String>();
 
 File root;
 File curFolder;
 
 final static int KERNAL_WIDTH = 3;
 final static int KERNAL_HEIGHT = 3;

 int[][] kernal_blur = {
   {1, 1, 1},
   {1, 1, 1},
   {1, 1, 1}
 };
 
 final static int DIV_BY_9 = 9;
 
 ImageView imageSource, imageAfter;
 Bitmap bitmap_Source;
 ProgressBar progressBar;
 
 private Handler handler;
 Bitmap afterProcess;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        imageSource = (ImageView)findViewById(R.id.imageSource);
        imageAfter = (ImageView)findViewById(R.id.imageAfter);
        progressBar = (ProgressBar)findViewById(R.id.progressBar);
        progressBar.setVisibility(View.GONE);
        
        bitmap_Source = BitmapFactory.decodeResource(getResources(), R.drawable.testpicture);

        handler = new Handler();
        //performImageProcessing(bitmap_Source);
        
        root = new File(Environment
          .getExternalStorageDirectory()
          .getAbsolutePath());
        
        curFolder = root;
    }
    
    private void performImageProcessing(Bitmap bmSrc){
     imageSource.setImageBitmap(bmSrc);
     progressBar.setVisibility(View.VISIBLE);
  imageAfter.setVisibility(View.GONE);
     StratBackgroundProcess(bmSrc);
    }
    
    private void StratBackgroundProcess(final Bitmap bm_src){
     
     Runnable runnable = new Runnable(){

   @Override
   public void run() {
    afterProcess = processingBitmap(bm_src, kernal_blur);
    handler.post(new Runnable(){

     @Override
     public void run() {
      progressBar.setVisibility(View.GONE);
      imageAfter.setVisibility(View.VISIBLE);
      imageAfter.setImageBitmap(afterProcess);
     }
     
    });
   }
     };
     new Thread(runnable).start();
    }
    
    private Bitmap processingBitmap(Bitmap src, int[][] knl){
     Bitmap dest = Bitmap.createBitmap(
       src.getWidth(), src.getHeight(), src.getConfig());
     
     int bmWidth = src.getWidth();
     int bmHeight = src.getHeight();
     int bmWidth_MINUS_2 = bmWidth - 2;
     int bmHeight_MINUS_2 = bmHeight - 2;
     int bmWidth_OFFSET_1 = 1;
     int bmHeight_OFFSET_1 = 1;
     
     for(int i = bmWidth_OFFSET_1; i <= bmWidth_MINUS_2; i++){
      for(int j = bmHeight_OFFSET_1; j <= bmHeight_MINUS_2; j++){
       
       //get the surround 7*7 pixel of current src[i][j] into a matrix subSrc[][]
       int[][] subSrc = new int[KERNAL_WIDTH][KERNAL_HEIGHT];
       for(int k = 0; k < KERNAL_WIDTH; k++){
        for(int l = 0; l < KERNAL_HEIGHT; l++){
         subSrc[k][l] = src.getPixel(i-bmWidth_OFFSET_1+k, j-bmHeight_OFFSET_1+l);
        }
       }
       
       //subSum = subSrc[][] * knl[][]
       long subSumA = 0;
       long subSumR = 0;
       long subSumG = 0;
       long subSumB = 0;

       for(int k = 0; k < KERNAL_WIDTH; k++){
        for(int l = 0; l < KERNAL_HEIGHT; l++){
         subSumA += (long)(Color.alpha(subSrc[k][l])) * (long)(knl[k][l]);
         subSumR += (long)(Color.red(subSrc[k][l])) * (long)(knl[k][l]);
         subSumG += (long)(Color.green(subSrc[k][l])) * (long)(knl[k][l]);
         subSumB += (long)(Color.blue(subSrc[k][l])) * (long)(knl[k][l]);
        }
       }
       
       subSumA = subSumA/DIV_BY_9;
       subSumR = subSumR/DIV_BY_9;
       subSumG = subSumG/DIV_BY_9;
       subSumB = subSumB/DIV_BY_9;
       
       int orgColor = src.getPixel(i, j);
       int orgA = Color.alpha(orgColor);
       int orgR = Color.red(orgColor);
       int orgG = Color.green(orgColor);
       int orgB = Color.blue(orgColor);
       
       subSumA = orgA + (orgA - subSumA);
       subSumR = orgR + (orgR - subSumR);
       subSumG = orgG + (orgG - subSumG);
       subSumB = orgB + (orgB - subSumB);

       if(subSumA <0){
        subSumA = 0;
       }else if(subSumA > 255){
        subSumA = 255; 
       }
       
       if(subSumR <0){
        subSumR = 0;
       }else if(subSumR > 255){
        subSumR = 255; 
       }
       
       if(subSumG <0){
        subSumG = 0;
       }else if(subSumG > 255){
        subSumG = 255;
       }
       
       if(subSumB <0){
        subSumB = 0;
       }else if(subSumB > 255){
        subSumB = 255;
       }

       dest.setPixel(i, j, Color.argb(
         (int)subSumA, 
         (int)subSumR, 
         (int)subSumG, 
         (int)subSumB));
      } 
     }
     
     return dest;
    }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  menu.add(0, 0, 0, "Load jpg");
  return true;
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  switch(item.getItemId()){
  case (0):
   showDialog(LOAD_DIALOG_ID);
   break;
  }
  return true;
 }

 @Override
 protected Dialog onCreateDialog(int id) {

  Dialog dialog = null;
  
  switch(id) {
     case LOAD_DIALOG_ID:
      dialog = new Dialog(AndroidImageProcessingActivity.this);
      dialog.setContentView(R.layout.load_dialog);
      dialog.setTitle("Load JPG");
      
      dialog.setCancelable(true);
      dialog.setCanceledOnTouchOutside(true);
      
      textFolder = (TextView)dialog.findViewById(R.id.folder);

      buttonUp = (Button)dialog.findViewById(R.id.up);
      buttonUp.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View v) {
     // TODO Auto-generated method stub
     ListDir(curFolder.getParentFile());
    }});

      //Prepare ListView in dialog
      dialog_ListView = (ListView)dialog.findViewById(R.id.dialoglist);

      dialog_ListView.setOnItemClickListener(new OnItemClickListener(){

    @Override
    public void onItemClick(AdapterView<?> parent, View view,
      int position, long id) {
     
     File selected = new File(fileList.get(position));
     if(selected.isDirectory()){
      ListDir(selected); 
     }else {
      Toast.makeText(AndroidImageProcessingActivity.this,
        selected.toString() + " selected",
        Toast.LENGTH_LONG).show();
      dismissDialog(LOAD_DIALOG_ID);
      
      Bitmap bm = BitmapFactory.decodeFile(selected.getAbsolutePath());
      imageSource.setImageBitmap(bm);
            performImageProcessing(bm);
        }
     
    }});
      
         break;
     }

  return dialog;
 }

 @Override
 protected void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
  // TODO Auto-generated method stub
  super.onPrepareDialog(id, dialog, bundle);

  switch(id) {
     case LOAD_DIALOG_ID:
      ListDir(curFolder);
         break;
     }
  
 }
 
 void ListDir(File f){

  if(f.equals(root)){
   buttonUp.setEnabled(false);
  }else{
   buttonUp.setEnabled(true);
  }
  
  curFolder = f;
  textFolder.setText(f.getPath());
  
  File[] files = f.listFiles();
  fileList.clear();
  for (File file : files){
   
   if(file.isDirectory()){
    fileList.add(file.getPath());
   }else{
    Uri selectedUri = Uri.fromFile(file);
    String fileExtension
     = MimeTypeMap.getFileExtensionFromUrl(selectedUri.toString());
    if(fileExtension.equalsIgnoreCase("jpg")){
     fileList.add(file.getPath());
    }
   }
   
  }
       
  ArrayAdapter<String> directoryList
   = new ArrayAdapter<String>(this,
     android.R.layout.simple_list_item_1, fileList);
  dialog_ListView.setAdapter(directoryList);
 }

}


Main.xml.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
    
    <HorizontalScrollView
     android:layout_width="wrap_content"
     android:layout_height="wrap_content">
     <ScrollView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content">
         <LinearLayout 
             android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:orientation="vertical" >
          <TextView
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:text="Original" />
       <ImageView
           android:id="@+id/imageSource" 
           android:layout_width="wrap_content"
           android:layout_height="wrap_content" 
           android:scaleType="center"/>
       <TextView
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:text="Result" />
       <FrameLayout 
           android:layout_width="wrap_content"
           android:layout_height="wrap_content">
           <ImageView
               android:id="@+id/imageAfter" 
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:scaleType="center"/>
           <ProgressBar
               android:id="@+id/progressBar"
               style="?android:attr/progressBarStyleLarge"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"/>
       </FrameLayout>
      
      </LinearLayout>
     </ScrollView>
 </HorizontalScrollView>

</LinearLayout>


May 30, 2012

JPG File Chooser

We are going to modify "custom file explorer dialog" in last article to open JPG file only. The selected JPG file will be loaded in a ImageView.

JPG File Chooser


Keep no change on dialoglayout.xml, refer last article "Implement custom file explorer dialog".

Main code:
package com.AndroidCustomDialog;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.app.Dialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.MimeTypeMap;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class AndroidCustomDialogActivity extends Activity {

 Button buttonOpenDialog;
 Button buttonUp;
 TextView textFolder;
 ImageView image;
 
 String KEY_TEXTPSS = "TEXTPSS";
 static final int CUSTOM_DIALOG_ID = 0;
 
 ListView dialog_ListView;
 
 File root;
 File curFolder;
 
 private List<String> fileList = new ArrayList<String>();
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        image = (ImageView)findViewById(R.id.image);

        buttonOpenDialog = (Button)findViewById(R.id.opendialog);
        buttonOpenDialog.setOnClickListener(new Button.OnClickListener(){

   @Override
   public void onClick(View arg0) {
    showDialog(CUSTOM_DIALOG_ID);
   }});

        root = new File(Environment
          .getExternalStorageDirectory()
          .getAbsolutePath());
        
        curFolder = root;
  
    }

 @Override
 protected Dialog onCreateDialog(int id) {

  Dialog dialog = null;
  
  switch(id) {
     case CUSTOM_DIALOG_ID:
      dialog = new Dialog(AndroidCustomDialogActivity.this);
      dialog.setContentView(R.layout.dialoglayout);
      dialog.setTitle("Select JPG");
      
      dialog.setCancelable(true);
      dialog.setCanceledOnTouchOutside(true);
      
      textFolder = (TextView)dialog.findViewById(R.id.folder);

      buttonUp = (Button)dialog.findViewById(R.id.up);
      buttonUp.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View v) {
     // TODO Auto-generated method stub
     ListDir(curFolder.getParentFile());
    }});

      //Prepare ListView in dialog
      dialog_ListView = (ListView)dialog.findViewById(R.id.dialoglist);

      dialog_ListView.setOnItemClickListener(new OnItemClickListener(){

    @Override
    public void onItemClick(AdapterView<?> parent, View view,
      int position, long id) {
     
     File selected = new File(fileList.get(position));
     if(selected.isDirectory()){
      ListDir(selected); 
     }else {
      Toast.makeText(AndroidCustomDialogActivity.this,
        selected.toString() + " selected",
        Toast.LENGTH_LONG).show();
      dismissDialog(CUSTOM_DIALOG_ID);
      
      Bitmap bm = BitmapFactory.decodeFile(selected.getAbsolutePath());
            image.setImageBitmap(bm);

        }
     
    }});
      
         break;
     }

  return dialog;
 }

 @Override
 protected void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
  // TODO Auto-generated method stub
  super.onPrepareDialog(id, dialog, bundle);

  switch(id) {
     case CUSTOM_DIALOG_ID:
      ListDir(curFolder);
         break;
     }
  
 }
 
 void ListDir(File f){

  if(f.equals(root)){
   buttonUp.setEnabled(false);
  }else{
   buttonUp.setEnabled(true);
  }
  
  curFolder = f;
  textFolder.setText(f.getPath());
  
  File[] files = f.listFiles();
  fileList.clear();
  for (File file : files){

   if(file.isDirectory()){
    fileList.add(file.getPath());
   }else{
    Uri selectedUri = Uri.fromFile(file);
    String fileExtension
     = MimeTypeMap.getFileExtensionFromUrl(selectedUri.toString());
    if(fileExtension.equalsIgnoreCase("jpg")){
     fileList.add(file.getPath());
    }
   }
   
  }
       
  ArrayAdapter<String> directoryList
   = new ArrayAdapter<String>(this,
     android.R.layout.simple_list_item_1, fileList);
  dialog_ListView.setAdapter(directoryList);
 }

}

Main layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />

    <Button
        android:id="@+id/opendialog"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Load JPG" />
 <ImageView
        android:id="@+id/image"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>


Related:
- Custom dialog to open folder, instead of individual file.


May 29, 2012

Implement custom file explorer dialog

It's a example modified from last article "Custom dialog with ListView" to implement file explorer.

custom file explorer dialog


dialoglayout.xml, the layout of the custom dialog.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/customdialog"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:padding="20dp"
    android:minWidth="300dp">
    <LinearLayout 
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp">
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_launcher"/>
        <Button
            android:id="@+id/up"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="Parent folder"/>
    </LinearLayout>

 <TextView
     android:id="@+id/folder"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <ListView
        android:id="@+id/dialoglist"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

</LinearLayout>


Main code:
package com.AndroidCustomDialog;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class AndroidCustomDialogActivity extends Activity {

 Button buttonOpenDialog;
 Button buttonUp;
 TextView textFolder;
 
 String KEY_TEXTPSS = "TEXTPSS";
 static final int CUSTOM_DIALOG_ID = 0;
 
 ListView dialog_ListView;
 
 File root;
 File curFolder;
 
 private List<String> fileList = new ArrayList<String>();
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        buttonOpenDialog = (Button)findViewById(R.id.opendialog);
        buttonOpenDialog.setOnClickListener(new Button.OnClickListener(){

   @Override
   public void onClick(View arg0) {
    showDialog(CUSTOM_DIALOG_ID);
   }});

        root = new File(Environment
          .getExternalStorageDirectory()
          .getAbsolutePath());
        
        curFolder = root;
  
    }

 @Override
 protected Dialog onCreateDialog(int id) {

  Dialog dialog = null;
  
  switch(id) {
     case CUSTOM_DIALOG_ID:
      dialog = new Dialog(AndroidCustomDialogActivity.this);
      dialog.setContentView(R.layout.dialoglayout);
      dialog.setTitle("Custom Dialog");
      
      dialog.setCancelable(true);
      dialog.setCanceledOnTouchOutside(true);
      
      textFolder = (TextView)dialog.findViewById(R.id.folder);

      buttonUp = (Button)dialog.findViewById(R.id.up);
      buttonUp.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View v) {
     // TODO Auto-generated method stub
     ListDir(curFolder.getParentFile());
    }});

      //Prepare ListView in dialog
      dialog_ListView = (ListView)dialog.findViewById(R.id.dialoglist);

      dialog_ListView.setOnItemClickListener(new OnItemClickListener(){

    @Override
    public void onItemClick(AdapterView<?> parent, View view,
      int position, long id) {
     
     File selected = new File(fileList.get(position));
     if(selected.isDirectory()){
      ListDir(selected); 
     }else {
      Toast.makeText(AndroidCustomDialogActivity.this,
        selected.toString() + " selected",
        Toast.LENGTH_LONG).show();
      dismissDialog(CUSTOM_DIALOG_ID);
        }
     
    }});
      
         break;
     }

  return dialog;
 }

 @Override
 protected void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
  // TODO Auto-generated method stub
  super.onPrepareDialog(id, dialog, bundle);

  switch(id) {
     case CUSTOM_DIALOG_ID:
      ListDir(curFolder);
         break;
     }
  
 }
 
 void ListDir(File f){

  if(f.equals(root)){
   buttonUp.setEnabled(false);
  }else{
   buttonUp.setEnabled(true);
  }
  
  curFolder = f;
  textFolder.setText(f.getPath());
  
  File[] files = f.listFiles();
  fileList.clear();
  for (File file : files){
   fileList.add(file.getPath());   
  }
       
  ArrayAdapter<String> directoryList
   = new ArrayAdapter<String>(this,
     android.R.layout.simple_list_item_1, fileList);
  dialog_ListView.setAdapter(directoryList);
 }

}


main layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />

    <Button
        android:id="@+id/opendialog"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Open Dialog" />

</LinearLayout>


Next:
- JPG File Chooser


May 28, 2012

Custom dialog with ListView

Custom dialog with ListView


Create /res/layout/dialoglayout.xml, it define the layout of the custom dialog with ListView.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/customdialog"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:padding="20dp"
    android:minWidth="300dp">
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher"/>

    <ListView
        android:id="@+id/dialoglist"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

</LinearLayout>


Java code of the main activity.
package com.AndroidCustomDialog;

import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnDismissListener;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class AndroidCustomDialogActivity extends Activity {

 Button buttonOpenDialog;
 
 String KEY_TEXTPSS = "TEXTPSS";
 static final int CUSTOM_DIALOG_ID = 0;
 
 ListView dialog_ListView;
 
 String[] listContent = {
   "January", "February", "March", "April", 
   "May", "June", "July", "August", "September", 
   "October", "November", "December"};
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        buttonOpenDialog = (Button)findViewById(R.id.opendialog);
        buttonOpenDialog.setOnClickListener(new Button.OnClickListener(){

   @Override
   public void onClick(View arg0) {
    showDialog(CUSTOM_DIALOG_ID);
   }});
  
    }

 @Override
 protected Dialog onCreateDialog(int id) {

  Dialog dialog = null;
  
  switch(id) {
     case CUSTOM_DIALOG_ID:
      dialog = new Dialog(AndroidCustomDialogActivity.this);
      dialog.setContentView(R.layout.dialoglayout);
      dialog.setTitle("Custom Dialog");
      
      dialog.setCancelable(true);
      dialog.setCanceledOnTouchOutside(true);
      
      dialog.setOnCancelListener(new OnCancelListener(){

    @Override
    public void onCancel(DialogInterface dialog) {
     // TODO Auto-generated method stub
     Toast.makeText(AndroidCustomDialogActivity.this, 
       "OnCancelListener", 
       Toast.LENGTH_LONG).show();
    }});
      
      dialog.setOnDismissListener(new OnDismissListener(){

    @Override
    public void onDismiss(DialogInterface dialog) {
     // TODO Auto-generated method stub
     Toast.makeText(AndroidCustomDialogActivity.this, 
       "OnDismissListener", 
       Toast.LENGTH_LONG).show();
    }});

      //Prepare ListView in dialog
      dialog_ListView = (ListView)dialog.findViewById(R.id.dialoglist);
      ArrayAdapter<String> adapter 
       = new ArrayAdapter<String>(this, 
         android.R.layout.simple_list_item_1, listContent);
      dialog_ListView.setAdapter(adapter);
      dialog_ListView.setOnItemClickListener(new OnItemClickListener(){

    @Override
    public void onItemClick(AdapterView<?> parent, View view,
      int position, long id) {
     // TODO Auto-generated method stub
     Toast.makeText(AndroidCustomDialogActivity.this, 
       parent.getItemAtPosition(position).toString() + " clicked", 
       Toast.LENGTH_LONG).show();
     dismissDialog(CUSTOM_DIALOG_ID);
    }});
      
         break;
     }

  return dialog;
 }

 @Override
 protected void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
  // TODO Auto-generated method stub
  super.onPrepareDialog(id, dialog, bundle);

  switch(id) {
     case CUSTOM_DIALOG_ID:
      //
         break;
     }
  
 }

}


main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />

    <Button
        android:id="@+id/opendialog"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Open Dialog" />

</LinearLayout>


Cancel and Dismiss Custom dialog

Last article demonstrate a basic custom dialog, it can be dismissed by buttons. How about cancelled by system BACK key and touching outside dialog? by calling setCancelable(boolean) and setCanceledOnTouchOutside(boolean) we can sets whether this dialog is cancelable with the BACK key, and whether this dialog is canceled when touched outside the window's bounds.

We also implement OnCancelListener and OnDismissListener to handle the cancel and dismiss events.

Cancel and Dismiss Custom dialog


Modify the main activity in the last article "Custom dialog, with data passed in Bundle".
package com.AndroidCustomDialog;

import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnDismissListener;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class AndroidCustomDialogActivity extends Activity {
 
 EditText editTextPass;
 Button buttonOpenDialog;
 
 String KEY_TEXTPSS = "TEXTPSS";
 static final int CUSTOM_DIALOG_ID = 0;
 TextView dialog_TextView;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        editTextPass = (EditText)findViewById(R.id.textpass);
        buttonOpenDialog = (Button)findViewById(R.id.opendialog);
        buttonOpenDialog.setOnClickListener(new Button.OnClickListener(){

   @Override
   public void onClick(View arg0) {
    // TODO Auto-generated method stub
    Bundle bundle = new Bundle();
    bundle.putString(KEY_TEXTPSS, editTextPass.getText().toString());
    showDialog(CUSTOM_DIALOG_ID, bundle);
   }});
  
    }

 @Override
 protected Dialog onCreateDialog(int id) {

  Dialog dialog = null;
  
  switch(id) {
     case CUSTOM_DIALOG_ID:
      dialog = new Dialog(AndroidCustomDialogActivity.this);
      dialog.setContentView(R.layout.dialoglayout);
      dialog.setTitle("Custom Dialog");
      dialog_TextView = (TextView)dialog.findViewById(R.id.dialogtext);
      
      Button dialog_OK = (Button)dialog.findViewById(R.id.dialog_ok);
      dialog_OK.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View v) {
     // TODO Auto-generated method stub
     Toast.makeText(AndroidCustomDialogActivity.this, 
       "Dismiss by OK button", 
       Toast.LENGTH_LONG).show();
     dismissDialog(CUSTOM_DIALOG_ID);
    }});
      
      Button dialog_Cancel = (Button)dialog.findViewById(R.id.dialog_cancel);
      dialog_Cancel.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View v) {
     // TODO Auto-generated method stub
     Toast.makeText(AndroidCustomDialogActivity.this, 
       "Dismiss by Cancel button", 
       Toast.LENGTH_LONG).show();
     dismissDialog(CUSTOM_DIALOG_ID);
    }});
      
      dialog.setCancelable(true);
      dialog.setCanceledOnTouchOutside(true);
      
      dialog.setOnCancelListener(new OnCancelListener(){

    @Override
    public void onCancel(DialogInterface dialog) {
     // TODO Auto-generated method stub
     Toast.makeText(AndroidCustomDialogActivity.this, 
       "OnCancelListener", 
       Toast.LENGTH_LONG).show();
    }});
      
      dialog.setOnDismissListener(new OnDismissListener(){

    @Override
    public void onDismiss(DialogInterface dialog) {
     // TODO Auto-generated method stub
     Toast.makeText(AndroidCustomDialogActivity.this, 
       "OnDismissListener", 
       Toast.LENGTH_LONG).show();
    }});
      
         break;
     }

  return dialog;
 }

 @Override
 protected void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
  // TODO Auto-generated method stub
  super.onPrepareDialog(id, dialog, bundle);

  switch(id) {
     case CUSTOM_DIALOG_ID:
      dialog_TextView.setText("Text passed to Dialog: " + bundle.getString(KEY_TEXTPSS));
         break;
     }
  
 }

}


May 27, 2012

Custom dialog, with data passed in Bundle

In this example, a custom dialog is create in onCreateDialog(). The text in the EditText will be passed to the custom dialog via a bundle in onPrepareDialog(int, Dialog, Bundle).

Custom dialog, with data passed in Bundle


If you use showDialog(int), the activity will call onCreateDialog() method the first time, and hang onto it thereafter. Any dialog that is created by this method will automatically be saved and restored for you, including whether it is showing.

If you would like an opportunity to prepare your dialog before it is shown, override onPrepareDialog(int, Dialog, Bundle). We will pass the text from main activity to our custom dialog, so we have to implement onPrepareDialog(int, Dialog, Bundle). And the text will be passed to dislog in bundle.

main activity
package com.AndroidCustomDialog;

import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class AndroidCustomDialogActivity extends Activity {
 
 EditText editTextPass;
 Button buttonOpenDialog;
 
 String KEY_TEXTPSS = "TEXTPSS";
 static final int CUSTOM_DIALOG_ID = 0;
 TextView dialog_TextView;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        editTextPass = (EditText)findViewById(R.id.textpass);
        buttonOpenDialog = (Button)findViewById(R.id.opendialog);
        buttonOpenDialog.setOnClickListener(new Button.OnClickListener(){

   @Override
   public void onClick(View arg0) {
    // TODO Auto-generated method stub
    Bundle bundle = new Bundle();
    bundle.putString(KEY_TEXTPSS, editTextPass.getText().toString());
    showDialog(CUSTOM_DIALOG_ID, bundle);
   }});
  
    }

 @Override
 protected Dialog onCreateDialog(int id) {

  Dialog dialog = null;
  
  switch(id) {
     case CUSTOM_DIALOG_ID:
      dialog = new Dialog(AndroidCustomDialogActivity.this);
      dialog.setContentView(R.layout.dialoglayout);
      dialog.setTitle("Custom Dialog");
      dialog_TextView = (TextView)dialog.findViewById(R.id.dialogtext);
      
      Button dialog_OK = (Button)dialog.findViewById(R.id.dialog_ok);
      dialog_OK.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View v) {
     // TODO Auto-generated method stub
     Toast.makeText(AndroidCustomDialogActivity.this, 
       "Dismiss by OK button", 
       Toast.LENGTH_LONG).show();
     dismissDialog(CUSTOM_DIALOG_ID);
    }});
      
      Button dialog_Cancel = (Button)dialog.findViewById(R.id.dialog_cancel);
      dialog_Cancel.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View v) {
     // TODO Auto-generated method stub
     Toast.makeText(AndroidCustomDialogActivity.this, 
       "Dismiss by Cancel button", 
       Toast.LENGTH_LONG).show();
     dismissDialog(CUSTOM_DIALOG_ID);
    }});
      
         break;
     }

  return dialog;
 }

 @Override
 protected void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
  // TODO Auto-generated method stub
  super.onPrepareDialog(id, dialog, bundle);

  switch(id) {
     case CUSTOM_DIALOG_ID:
      dialog_TextView.setText("Text passed to Dialog: " + bundle.getString(KEY_TEXTPSS));
         break;
     }
  
 }

}


main layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
    <EditText
        android:id="@+id/textpass"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/opendialog"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Open Dialog" />

</LinearLayout>

/res/layout/dialoglayout.xml, it's the layout of the custom dialog.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/customdialog"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:padding="20dp">
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher"/>
    <TextView
        android:id="@+id/dialogtext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/dialog_ok"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="OK" />
    <Button
        android:id="@+id/dialog_cancel"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="cancel" />

</LinearLayout>


Next:
- Cancel and Dismiss Custom dialog


May 26, 2012

Auto resize bitmap to fit View

Last article demonstrate Reduce bitmap by setting inSampleSize of BitmapFactory.Options manually. In this article we are going to calculate the most optimize inSampleSize value to fit within a area. In our case, the ImageView is place inside a LinearLayout with "fill_parent" on both layout_width and layout_height. We try to get the best inSampleSize to create bitmap most fit in the LinearLayout.

Auto resize to fit View


In order to calulate the resize ratio, we need the expected width and height of the parent LinearLayout. BUT we cannot direct get it in onCreate() call-back method. Because when onCreate() is called, the layout not yet drawn and both getWidth() and getHeight() return 0. In the code, I display the getWidth() and getHeight() in onCreate(), onStart() and onResume() for your reference. So...how can I know when the layout is drawn? it's android.view.ViewTreeObserver.

android.view.ViewTreeObserver is used to register listeners that can be notified of global changes in the view tree. Such global events include, but are not limited to, layout of the whole tree, beginning of the drawing pass, touch mode change.... A ViewTreeObserver should never be instantiated by applications as it is provided by the views hierarchy. Refer to getViewTreeObserver() for more information.


onGlobalLayout() of ViewTreeObserver.OnGlobalLayoutListener is Callback method to be invoked when the global layout state or the visibility of views within the view tree changes.

Such that we can register our OnGlobalLayoutListener to get parent LinearLayout width and height, to perform the auto-resize.

(I also tried to do it in onPreDraw() of ViewTreeObserver.onPreDrawListener. But it seem that to be called too many times!)

Please note that inSampleSize is int value, that means we cannot get a 100% fit value. That's why I place HorizontalScrollView and ScrollView outside the ImageView, you can still scroll it. If you want a visually full-fit ImageView, simple remove HorizontalScrollView and ScrollView.

package com.AndroidResizeBigPicture;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

public class AndroidResizeBigPictureActivity extends Activity {
 

 TextView resizeLevel;
 ImageView imageView;
 LinearLayout imageParent;
 
 Bitmap bitmap_Source;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

     resizeLevel = (TextView)findViewById(R.id.resizelevel);
     imageView = (ImageView)findViewById(R.id.imageview);
     imageParent = (LinearLayout)findViewById(R.id.imageparent);

     ToastInfo("onCreate");
     
     ViewTreeObserver viewTreeObserver = imageParent.getViewTreeObserver();
     viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener);
     //viewTreeObserver.addOnPreDrawListener(onPreDrawListener);
    }
    
    OnGlobalLayoutListener onGlobalLayoutListener
    = new OnGlobalLayoutListener(){

  @Override
  public void onGlobalLayout() {
   /*
    * Callback method to be invoked when the global layout state 
    * or the visibility of views within the view tree changes
    */
   ToastInfo("onGlobalLayout");
   imageView.setImageBitmap(
     resize(imageParent.getWidth(), imageParent.getHeight()));
  }
     
    };
    
    OnPreDrawListener onPreDrawListener
    = new OnPreDrawListener(){

  @Override
  public boolean onPreDraw() {
   /*
    * Callback method to be invoked when the view tree is about 
    * to be drawn. At this point, all views in the tree have been 
    * measured and given a frame. Clients can use this to adjust 
    * their scroll bounds or even to request a new layout before 
    * drawing occurs.
    */
   
   ToastInfo("onPreDraw");
   imageView.setImageBitmap(
     resize(imageParent.getWidth(), imageParent.getHeight()));
   
   return true;
  }};
  
    private void ToastInfo(String caller){
     Toast.makeText(getBaseContext(), 
       caller + " - " +
       imageParent.getWidth() + " : " + imageParent.getHeight(), 
       Toast.LENGTH_LONG).show();
    }

 @Override
 protected void onStart() {
  // TODO Auto-generated method stub
  super.onStart();
  ToastInfo("onStart");
 }
 
 @Override
 protected void onResume() {
  // TODO Auto-generated method stub
  super.onResume();
  ToastInfo("onResume");
 }

 private Bitmap resize(int width, int height){
  
  BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
        bmpFactoryOptions.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeResource(
          getResources(), R.drawable.testpicture, bmpFactoryOptions);
        
        int heightRatio = (int)Math.ceil(bmpFactoryOptions.outHeight/(float)height);
        int widthRatio = (int)Math.ceil(bmpFactoryOptions.outWidth/(float)width);
        
        if (heightRatio > 1 || widthRatio > 1)
        {
         if (heightRatio > widthRatio)
         {
          bmpFactoryOptions.inSampleSize = heightRatio;
          resizeLevel.setText("inSampleSize= " + heightRatio);
         } else {
          bmpFactoryOptions.inSampleSize = widthRatio; 
          resizeLevel.setText("inSampleSize= " + widthRatio);
         } 
        }else{
         resizeLevel.setText("inSampleSize= N/A");
        }
         
        bmpFactoryOptions.inJustDecodeBounds = false;
        bitmap = BitmapFactory.decodeResource(getResources(), 
          R.drawable.testpicture, bmpFactoryOptions);
        return bitmap;
 }
  
}


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
 
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
    <TextView
        android:id="@+id/resizelevel"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content" />
    <LinearLayout
        android:id="@+id/imageparent"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >
        <HorizontalScrollView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content">
       <ScrollView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content">
          <ImageView
              android:id="@+id/imageview"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:scaleType="center" />
         </ScrollView>
        </HorizontalScrollView>
    </LinearLayout>
 
</LinearLayout>


Notice: You have to place your testpicture.jpg in /res/drawable/ folder.


May 25, 2012

Reduce bitmap by setting inSampleSize of BitmapFactory.Options

if inSampleSize of BitmapFactory.Options set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory. The sample size is the number of pixels in either dimension that correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is treated the same as 1.

Note: the decoder will try to fulfill this request, but the resulting bitmap may have different dimensions that precisely what has been requested. ~ in my testing, both 1/2 and 1/3 have the same size bitmap generated.

Also, powers of 2 are often faster/easier for the decoder to honor.

This example demonstrate how to create a resized bitmap by user setting.

Reduce bitmap by setting inSampleSize of BitmapFactory.Options


package com.AndroidResizeBigPicture;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;

public class AndroidResizeBigPictureActivity extends Activity {
 
 SeekBar resizeBar;
 TextView resizeLevel;
 ImageView imageView;
 
 Bitmap bitmap_Source;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        resizeBar = (SeekBar)findViewById(R.id.resizebar);
     resizeLevel = (TextView)findViewById(R.id.resizelevel);
     imageView = (ImageView)findViewById(R.id.imageview);
     
     resizeBar.setOnSeekBarChangeListener(resizeBarChangeListener);
     
     bitmap_Source = BitmapFactory.decodeResource(getResources(), R.drawable.testpicture);
    }
    
    OnSeekBarChangeListener resizeBarChangeListener
    = new OnSeekBarChangeListener(){

  @Override
  public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
   // TODO Auto-generated method stub
   int ratio = (int)java.lang.Math.pow(2, progress);
   imageView.setImageBitmap(resize(ratio));
   resizeLevel.setText("ratio: 1/" + ratio);
  }

  @Override
  public void onStartTrackingTouch(SeekBar seekBar) {
   // TODO Auto-generated method stub
   
  }

  @Override
  public void onStopTrackingTouch(SeekBar seekBar) {
   // TODO Auto-generated method stub
   
  }};
  
 private Bitmap resize(int ratio){
  BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
        bmpFactoryOptions.inSampleSize = ratio;        
        bmpFactoryOptions.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(getResources(), R.drawable.testpicture, bmpFactoryOptions);
 }
  
}


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
 
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
    <SeekBar
        android:id="@+id/resizebar"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:max="4"
  android:progress="0"
  android:layout_margin="10dp"/>
    <TextView
        android:id="@+id/resizelevel"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="ratio: 1/1"/>
    <HorizontalScrollView
     android:layout_width="wrap_content"
     android:layout_height="wrap_content">
     <ScrollView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content">
         <LinearLayout
             android:layout_width="fill_parent"
             android:layout_height="fill_parent"
             android:orientation="vertical" >
             <ImageView
                 android:id="@+id/imageview"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:scaleType="center"
                 android:src="@drawable/testpicture"/>       
      </LinearLayout>
     </ScrollView>
 </HorizontalScrollView>
 
</LinearLayout>


Notice: You have to place your testpicture.jpg in /res/drawable/ folder.


Next:
- Auto resize to fit View


May 22, 2012

Unsharp Mask (USM) on Android Image Processing

Read it first: Image processing on Android, the basic logic of Convolution Matrix.

The Unsharp Mask filter sharpens edges of the elements without increasing noise or blemish. Refer to GIMP doc to know How does an unsharp mask work.

As my understanding, for each pixel = original + (original - blur)

In this code, I use the blured image from "Apply Blur effect on Android, using Convolution Matrix" to obtain the USM image. In order to make it obviously, I apply the effect three times.

Unsharp Mask (USM) on Android Image Processing


package com.AndroidImageProcessing;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;

public class AndroidImageProcessingActivity extends Activity {
 
 final static int KERNAL_WIDTH = 3;
 final static int KERNAL_HEIGHT = 3;

 int[][] kernal_blur = {
   {1, 1, 1},
   {1, 1, 1},
   {1, 1, 1}
 };
 
 final static int DIV_BY_9 = 9;
 
 ImageView imageSource, imageAfter;
 Bitmap bitmap_Source;
 ProgressBar progressBar;
 
 private Handler handler;
 Bitmap afterProcess;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        imageSource = (ImageView)findViewById(R.id.imageSource);
        imageAfter = (ImageView)findViewById(R.id.imageAfter);
        progressBar = (ProgressBar)findViewById(R.id.progressBar);
        
        bitmap_Source = BitmapFactory.decodeResource(getResources(), R.drawable.testpicture);

        handler = new Handler();
        StratBackgroundProcess();
    }
    
    private void StratBackgroundProcess(){
     
     Runnable runnable = new Runnable(){

   @Override
   public void run() {
    afterProcess = processingBitmap(bitmap_Source, kernal_blur);
    afterProcess = processingBitmap(afterProcess, kernal_blur);
    afterProcess = processingBitmap(afterProcess, kernal_blur);
    handler.post(new Runnable(){

     @Override
     public void run() {
      progressBar.setVisibility(View.GONE);
      imageAfter.setImageBitmap(afterProcess);
     }
     
    });
   }
     };
     new Thread(runnable).start();
    }
    
    private Bitmap processingBitmap(Bitmap src, int[][] knl){
     Bitmap dest = Bitmap.createBitmap(
       src.getWidth(), src.getHeight(), src.getConfig());
     
     int bmWidth = src.getWidth();
     int bmHeight = src.getHeight();
     int bmWidth_MINUS_2 = bmWidth - 2;
     int bmHeight_MINUS_2 = bmHeight - 2;
     int bmWidth_OFFSET_1 = 1;
     int bmHeight_OFFSET_1 = 1;
     
     for(int i = bmWidth_OFFSET_1; i <= bmWidth_MINUS_2; i++){
      for(int j = bmHeight_OFFSET_1; j <= bmHeight_MINUS_2; j++){
       
       //get the surround 7*7 pixel of current src[i][j] into a matrix subSrc[][]
       int[][] subSrc = new int[KERNAL_WIDTH][KERNAL_HEIGHT];
       for(int k = 0; k < KERNAL_WIDTH; k++){
        for(int l = 0; l < KERNAL_HEIGHT; l++){
         subSrc[k][l] = src.getPixel(i-bmWidth_OFFSET_1+k, j-bmHeight_OFFSET_1+l);
        }
       }
       
       //subSum = subSrc[][] * knl[][]
       long subSumA = 0;
       long subSumR = 0;
       long subSumG = 0;
       long subSumB = 0;

       for(int k = 0; k < KERNAL_WIDTH; k++){
        for(int l = 0; l < KERNAL_HEIGHT; l++){
         subSumA += (long)(Color.alpha(subSrc[k][l])) * (long)(knl[k][l]);
         subSumR += (long)(Color.red(subSrc[k][l])) * (long)(knl[k][l]);
         subSumG += (long)(Color.green(subSrc[k][l])) * (long)(knl[k][l]);
         subSumB += (long)(Color.blue(subSrc[k][l])) * (long)(knl[k][l]);
        }
       }
       
       subSumA = subSumA/DIV_BY_9;
       subSumR = subSumR/DIV_BY_9;
       subSumG = subSumG/DIV_BY_9;
       subSumB = subSumB/DIV_BY_9;
       
       int orgColor = src.getPixel(i, j);
       int orgA = Color.alpha(orgColor);
       int orgR = Color.red(orgColor);
       int orgG = Color.green(orgColor);
       int orgB = Color.blue(orgColor);
       
       subSumA = orgA + (orgA - subSumA);
       subSumR = orgR + (orgR - subSumR);
       subSumG = orgG + (orgG - subSumG);
       subSumB = orgB + (orgB - subSumB);

       if(subSumA <0){
        subSumA = 0;
       }else if(subSumA > 255){
        subSumA = 255; 
       }
       
       if(subSumR <0){
        subSumR = 0;
       }else if(subSumR > 255){
        subSumR = 255; 
       }
       
       if(subSumG <0){
        subSumG = 0;
       }else if(subSumG > 255){
        subSumG = 255;
       }
       
       if(subSumB <0){
        subSumB = 0;
       }else if(subSumB > 255){
        subSumB = 255;
       }

       dest.setPixel(i, j, Color.argb(
         (int)subSumA, 
         (int)subSumR, 
         (int)subSumG, 
         (int)subSumB));
      } 
     }
     
     return dest;
    }

}


Using the main.xml in last article "Display image in full size".

Next:
- Image Processing with open JPG file dialog


Display image in full size

In the former articles in Image processing on Android series, the image is displayed in shrinked version to suit the device's screen. It's modified display in scrollable true size.

Display big image in full size


Modify the layout, main.xml, to embed the ImageViews inside nested HorizontalScrollView and ScrollView. Such that the ImageViews will be displayed in full-size and scrollable.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
    
    <HorizontalScrollView
     android:layout_width="wrap_content"
     android:layout_height="wrap_content">
     <ScrollView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content">
         <LinearLayout 
             android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:orientation="vertical" >
          <TextView
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:text="Original" />
       <ImageView
           android:id="@+id/imageSource" 
           android:layout_width="wrap_content"
           android:layout_height="wrap_content" 
           android:scaleType="center"
           android:src="@drawable/testpicture"/>
       <TextView
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:text="Result" />
       <FrameLayout 
           android:layout_width="wrap_content"
           android:layout_height="wrap_content">
           <ImageView
               android:id="@+id/imageAfter" 
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:scaleType="center"/>
           <ProgressBar
               android:id="@+id/progressBar"
               style="?android:attr/progressBarStyleLarge"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"/>
       </FrameLayout>
      
      </LinearLayout>
     </ScrollView>
 </HorizontalScrollView>

</LinearLayout>



May 21, 2012

Switch Activity once a marker on a MapView tapped

This example continuously work on the article "Handle both onTap(GeoPoint p, MapView mapView) and onTap(int index) implemented in MapView": When a marker on the MapView tapped, it will switch to another Activity with some data passes; Title, Latitude and Longitude.

Switch Activity once a marker on a MapView tapped


Create a new Android project target with Google APIs.

In order to use MapView on your app, you have to obtain your Map API Key, refer http://android-coding.blogspot.com/2011/06/mapview-and-maps-api-key.html

The main layout, main.xml. You have to replace your own Map API Key in com.google.android.maps.MapView. (It's same as before in "Handle both onTap(GeoPoint p, MapView mapView) and onTap(int index) implemented in MapView")
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
 <com.google.android.maps.MapView
     android:id="@+id/mapview"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     android:clickable="true"
     android:apiKey="--- Your own Map API Key ---" />
 
</LinearLayout>

The main part to be changed is in onTap(int index) method of MyItemizedOverlay.java
package com.AndroidMapView;
import java.util.ArrayList;

import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.widget.Toast;
 
import com.google.android.maps.GeoPoint;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.MapView;
import com.google.android.maps.OverlayItem;
 
public class MyItemizedOverlay extends ItemizedOverlay<OverlayItem> {
 
 private ArrayList<OverlayItem> overlayItemList = new ArrayList<OverlayItem>();
 Context context;
 
 public MyItemizedOverlay(Drawable marker, Context c) {
  super(boundCenterBottom(marker));
  // TODO Auto-generated constructor stub
  populate();
  context = c; 
 }
 
 @Override
 public boolean onTap(GeoPoint p, MapView mapView) {
  // TODO Auto-generated method stub
  if(super.onTap(p, mapView)){
   return true; 
  }
     
  String title = "pt:" + String.valueOf(overlayItemList.size() + 1);
  String snippet = "geo:\n"
    + String.valueOf(p.getLatitudeE6()) + "\n"
    + String.valueOf(p.getLongitudeE6());
     
  addItem(p, title, snippet);
     
  return true;
 }

 @Override
 protected boolean onTap(int index) {
  // TODO Auto-generated method stub
  //return super.onTap(index);

  Toast.makeText(context,
    "Touch on marker: \n" + overlayItemList.get(index).getTitle(),
    Toast.LENGTH_LONG).show();
  //return true; 
  
  Intent intent=new Intent(context, NewActivity.class);
  intent.putExtra("Title", overlayItemList.get(index).getTitle());
  intent.putExtra("LatE6", overlayItemList.get(index).getPoint().getLatitudeE6());
  intent.putExtra("LonE6", overlayItemList.get(index).getPoint().getLongitudeE6());
  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  context.startActivity(intent);
  return true;
 }
 
 public void addItem(GeoPoint p, String title, String snippet){
  OverlayItem newItem = new OverlayItem(p, title, snippet);
  overlayItemList.add(newItem);
  populate(); 
 }
 
 @Override
 protected OverlayItem createItem(int i) {
  // TODO Auto-generated method stub
  return overlayItemList.get(i); 
 }
 
 @Override
 public int size() {
  // TODO Auto-generated method stub
  return overlayItemList.size(); 
 }
 
 @Override
 public void draw(Canvas canvas, MapView mapView, boolean shadow) {
  // TODO Auto-generated method stub
  super.draw(canvas, mapView, shadow); 
 }
}

The main Activity, AndroidMapViewActivity.java (same as before)
package com.AndroidMapView;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
 
import android.graphics.drawable.Drawable;
import android.os.Bundle;
 
public class AndroidMapViewActivity extends MapActivity {
 
 /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  MapView mapView = (MapView) findViewById(R.id.mapview);
  mapView.setBuiltInZoomControls(true);
  
  Drawable marker=getResources().getDrawable(android.R.drawable.star_big_on);
  int markerWidth = marker.getIntrinsicWidth();
  int markerHeight = marker.getIntrinsicHeight();
  marker.setBounds(0, markerHeight, markerWidth, 0);
 
  MyItemizedOverlay myItemizedOverlay
   = new MyItemizedOverlay(marker, AndroidMapViewActivity.this);
  mapView.getOverlays().add(myItemizedOverlay);
  
  GeoPoint myPoint1 = new GeoPoint(0*1000000, 0*1000000);
  myItemizedOverlay.addItem(myPoint1, "myPoint1", "myPoint1");
  GeoPoint myPoint2 = new GeoPoint(50*1000000, 50*1000000);
  myItemizedOverlay.addItem(myPoint2, "myPoint2", "myPoint2"); 
 }
 
 @Override
 protected boolean isRouteDisplayed() {
  // TODO Auto-generated method stub
  return false; 
 }
}

NewActivity.java, the new activity to be opened once marker tapped. It simple retrieve the passed data and display it.
package com.AndroidMapView;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class NewActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  // TODO Auto-generated method stub
  super.onCreate(savedInstanceState);
  TextView textView = new TextView(this);
  setContentView(textView);
  
  Bundle bundle = this.getIntent().getExtras();
  textView.setText(
    bundle.getCharSequence("Title") + "@\n"
    + bundle.getInt("LatE6") + " : " + bundle.getInt("LonE6"));
     
 }

}

AndroidManifest.xml. You have to add the <activity> of NewActivity. You also have to include uses-library of "com.google.android.maps", and uses-permission of "android.permission.INTERNET".
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.AndroidMapView"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="10" />
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <uses-library android:name="com.google.android.maps" />
        <activity
            android:name=".AndroidMapViewActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".NewActivity"
            android:label="New" >
        </activity>
    </application>

</manifest>


May 18, 2012

Android image processing - Gaussian Blur

Read it first: Image processing on Android, the basic logic of Convolution Matrix.

In this example, the Sample Gaussian matrix from the article of Wikipedia - Gaussian blur is used as the 7 x 7 kernal to convolve image to apply Gaussian blur.

Gaussian Blur


package com.AndroidImageProcessing;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;

public class AndroidImageProcessingActivity extends Activity {
 
 final static int KERNAL_WIDTH = 7;
 final static int KERNAL_HEIGHT = 7;

 int[][] kernal_GaussianMatrix = {
   {67,   2292,   19117,   38771,   19117,   2292,   67  },
   {2292,   78633,   655965,  1330373,  655965,  78633,   2292 },
   {19117,  655965,  5472157,  11098164,  5472157,  655965,  19117 },
   {38771,  1330373,  11098164,  22508352,  11098164,  1330373,  38771 },
   {19117,  655965,  5472157,  11098164,  5472157,  655965,  19117 },
   {2292,   78633,   655965,  1330373,  655965,  78633,   2292 },
   {67,   2292,   19117,   38771,   19117,   2292,   67  }
 };
 
 ImageView imageSource, imageAfter;
 Bitmap bitmap_Source;
 ProgressBar progressBar;
 
 private Handler handler;
 Bitmap afterProcess;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        imageSource = (ImageView)findViewById(R.id.imageSource);
        imageAfter = (ImageView)findViewById(R.id.imageAfter);
        progressBar = (ProgressBar)findViewById(R.id.progressBar);
        
        bitmap_Source = BitmapFactory.decodeResource(getResources(), R.drawable.testpicture);

        handler = new Handler();
        StratBackgroundProcess();
    }
    
    private void StratBackgroundProcess(){
     
     Runnable runnable = new Runnable(){

   @Override
   public void run() {
    afterProcess = processingBitmap(bitmap_Source, kernal_GaussianMatrix);
    
    handler.post(new Runnable(){

     @Override
     public void run() {
      progressBar.setVisibility(View.GONE);
      imageAfter.setImageBitmap(afterProcess);
     }
     
    });
   }
     };
     new Thread(runnable).start();
    }
    
    private Bitmap processingBitmap(Bitmap src, int[][] knl){
     Bitmap dest = Bitmap.createBitmap(
       src.getWidth(), src.getHeight(), src.getConfig());
     
     int bmWidth = src.getWidth();
     int bmHeight = src.getHeight();
     int bmWidth_MINUS_6 = bmWidth - 6;
     int bmHeight_MINUS_6 = bmHeight - 6;
     int bmWidth_OFFSET_3 = 3;
     int bmHeight_OFFSET_3 = 3;
     
     for(int i = bmWidth_OFFSET_3; i <= bmWidth_MINUS_6; i++){
      for(int j = bmHeight_OFFSET_3; j <= bmHeight_MINUS_6; j++){
       
       //get the surround 7*7 pixel of current src[i][j] into a matrix subSrc[][]
       int[][] subSrc = new int[KERNAL_WIDTH][KERNAL_HEIGHT];
       for(int k = 0; k < KERNAL_WIDTH; k++){
        for(int l = 0; l < KERNAL_HEIGHT; l++){
         subSrc[k][l] = src.getPixel(i-bmWidth_OFFSET_3+k, j-bmHeight_OFFSET_3+l);
        }
       }
       
       //subSum = subSrc[][] * knl[][]
       long subSumR = 0;
       long subSumG = 0;
       long subSumB = 0;

       for(int k = 0; k < KERNAL_WIDTH; k++){
        for(int l = 0; l < KERNAL_HEIGHT; l++){
         subSumR += (long)(Color.red(subSrc[k][l])) * (long)(knl[k][l]);
         subSumG += (long)(Color.green(subSrc[k][l])) * (long)(knl[k][l]);
         subSumB += (long)(Color.blue(subSrc[k][l])) * (long)(knl[k][l]);
        }
       }
       
       subSumR = subSumR/100000000;
       subSumG = subSumG/100000000;
       subSumB = subSumB/100000000;

       if(subSumR <0){
        subSumR = 0;
       }else if(subSumR > 255){
        subSumR = 255; 
       }
       
       if(subSumG <0){
        subSumG = 0;
       }else if(subSumG > 255){
        subSumG = 255;
       }
       
       if(subSumB <0){
        subSumB = 0;
       }else if(subSumB > 255){
        subSumB = 255;
       }

       dest.setPixel(i, j, Color.argb(
         Color.alpha(src.getPixel(i, j)), 
         (int)subSumR, 
         (int)subSumG, 
         (int)subSumB));
      } 
     }
     
     return dest;
    }

}


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
    <ScrollView 
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        <LinearLayout 
            android:layout_width="fill_parent"
         android:layout_height="fill_parent"
         android:orientation="vertical" >
         <TextView
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:text="Original" />
      <ImageView
          android:id="@+id/imageSource" 
          android:layout_width="wrap_content"
          android:layout_height="wrap_content" 
          android:src="@drawable/testpicture"/>
      <TextView
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:text="Result" />
      <FrameLayout 
          android:layout_width="wrap_content"
          android:layout_height="wrap_content">
          <ImageView
              android:id="@+id/imageAfter" 
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"/>
          <ProgressBar
              android:id="@+id/progressBar"
              style="?android:attr/progressBarStyleLarge"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"/>
      </FrameLayout>
      
     </LinearLayout>
    </ScrollView>

</LinearLayout>


Android image processing - Edge Detect

Read it first: Image processing on Android, the basic logic of Convolution Matrix. 

It's example to perform edge enhance on Android.

Edge Detect


package com.AndroidImageProcessing;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;

public class AndroidImageProcessingActivity extends Activity {
 
 final static int KERNAL_WIDTH = 3;
 final static int KERNAL_HEIGHT = 3;
 
 int[][] kernal ={
   {0, -1, 0},
   {-1, 4, -1},
   {0, -1, 0}
 };
 
 ImageView imageSource, imageAfter;
 Bitmap bitmap_Source;
 ProgressBar progressBar;
 
 private Handler handler;
 Bitmap afterProcess;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        imageSource = (ImageView)findViewById(R.id.imageSource);
        imageAfter = (ImageView)findViewById(R.id.imageAfter);
        progressBar = (ProgressBar)findViewById(R.id.progressBar);
        
        bitmap_Source = BitmapFactory.decodeResource(getResources(), R.drawable.testpicture);

        handler = new Handler();
        StratBackgroundProcess();
    }
    
    private void StratBackgroundProcess(){
     
     Runnable runnable = new Runnable(){

   @Override
   public void run() {
    afterProcess = processingBitmap(bitmap_Source, kernal);
    
    handler.post(new Runnable(){

     @Override
     public void run() {
      progressBar.setVisibility(View.GONE);
      imageAfter.setImageBitmap(afterProcess);
     }
     
    });
   }
     };
     new Thread(runnable).start();
    }
    
    private Bitmap processingBitmap(Bitmap src, int[][] knl){
     Bitmap dest = Bitmap.createBitmap(
       src.getWidth(), src.getHeight(), src.getConfig());
     
     int bmWidth = src.getWidth();
     int bmHeight = src.getHeight();
     int bmWidth_MINUS_2 = bmWidth - 2;
     int bmHeight_MINUS_2 = bmHeight - 2;
     
     for(int i = 1; i <= bmWidth_MINUS_2; i++){
      for(int j = 1; j <= bmHeight_MINUS_2; j++){
       
       //get the surround 3*3 pixel of current src[i][j] into a matrix subSrc[][]
       int[][] subSrc = new int[KERNAL_WIDTH][KERNAL_HEIGHT];
       for(int k = 0; k < KERNAL_WIDTH; k++){
        for(int l = 0; l < KERNAL_HEIGHT; l++){
         subSrc[k][l] = src.getPixel(i-1+k, j-1+l);
        }
       }
       
       //subSum = subSrc[][] * knl[][]
       int subSumA = 0;
       int subSumR = 0;
       int subSumG = 0;
       int subSumB = 0;

       for(int k = 0; k < KERNAL_WIDTH; k++){
        for(int l = 0; l < KERNAL_HEIGHT; l++){
         subSumR += Color.red(subSrc[k][l]) * knl[k][l];
         subSumG += Color.green(subSrc[k][l]) * knl[k][l];
         subSumB += Color.blue(subSrc[k][l]) * knl[k][l];
        }
       }

       subSumA = Color.alpha(src.getPixel(i, j));

       if(subSumR <0){
        subSumR = 0;
       }else if(subSumR > 255){
        subSumR = 255; 
       }
       
       if(subSumG <0){
        subSumG = 0;
       }else if(subSumG > 255){
        subSumG = 255;
       }
       
       if(subSumB <0){
        subSumB = 0;
       }else if(subSumB > 255){
        subSumB = 255;
       }

       dest.setPixel(i, j, Color.argb(
         subSumA, 
         subSumR, 
         subSumG, 
         subSumB));
      } 
     }
     
     return dest;
    }

}


main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
    <ScrollView 
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        <LinearLayout 
            android:layout_width="fill_parent"
         android:layout_height="fill_parent"
         android:orientation="vertical" >
         <TextView
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:text="Original" />
      <ImageView
          android:id="@+id/imageSource" 
          android:layout_width="wrap_content"
          android:layout_height="wrap_content" 
          android:src="@drawable/testpicture"/>
      <TextView
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:text="Result" />
      <FrameLayout 
          android:layout_width="wrap_content"
          android:layout_height="wrap_content">
          <ImageView
              android:id="@+id/imageAfter" 
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"/>
          <ProgressBar
              android:id="@+id/progressBar"
              style="?android:attr/progressBarStyleLarge"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"/>
      </FrameLayout>
      
     </LinearLayout>
    </ScrollView>

</LinearLayout>



May 17, 2012

Android image processing - Edge Enhance

Read it first: Image processing on Android, the basic logic of Convolution Matrix.

It's example to perform edge enhance on Android.

Edge Enhance


package com.AndroidImageProcessing;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;

public class AndroidImageProcessingActivity extends Activity {
 
 final static int KERNAL_WIDTH = 3;
 final static int KERNAL_HEIGHT = 3;
 
 int[][] kernal ={
   {0, 0, 0},
   {-1, 1, 0},
   {0, 0, 0}
 };
 
 ImageView imageSource, imageAfter;
 Bitmap bitmap_Source;
 ProgressBar progressBar;
 
 private Handler handler;
 Bitmap afterProcess;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        imageSource = (ImageView)findViewById(R.id.imageSource);
        imageAfter = (ImageView)findViewById(R.id.imageAfter);
        progressBar = (ProgressBar)findViewById(R.id.progressBar);
        
        bitmap_Source = BitmapFactory.decodeResource(getResources(), R.drawable.testpicture);

        handler = new Handler();
        StratBackgroundProcess();
    }
    
    private void StratBackgroundProcess(){
     
     Runnable runnable = new Runnable(){

   @Override
   public void run() {
    afterProcess = processingBitmap(bitmap_Source, kernal);
    
    handler.post(new Runnable(){

     @Override
     public void run() {
      progressBar.setVisibility(View.GONE);
      imageAfter.setImageBitmap(afterProcess);
     }
     
    });
   }
     };
     new Thread(runnable).start();
    }
    
    private Bitmap processingBitmap(Bitmap src, int[][] knl){
     Bitmap dest = Bitmap.createBitmap(
       src.getWidth(), src.getHeight(), src.getConfig());
     
     int bmWidth = src.getWidth();
     int bmHeight = src.getHeight();
     int bmWidth_MINUS_2 = bmWidth - 2;
     int bmHeight_MINUS_2 = bmHeight - 2;
     
     for(int i = 1; i <= bmWidth_MINUS_2; i++){
      for(int j = 1; j <= bmHeight_MINUS_2; j++){
       
       //get the surround 3*3 pixel of current src[i][j] into a matrix subSrc[][]
       int[][] subSrc = new int[KERNAL_WIDTH][KERNAL_HEIGHT];
       for(int k = 0; k < KERNAL_WIDTH; k++){
        for(int l = 0; l < KERNAL_HEIGHT; l++){
         subSrc[k][l] = src.getPixel(i-1+k, j-1+l);
        }
       }
       
       //subSum = subSrc[][] * knl[][]
       int subSumA = 0;
       int subSumR = 0;
       int subSumG = 0;
       int subSumB = 0;

       for(int k = 0; k < KERNAL_WIDTH; k++){
        for(int l = 0; l < KERNAL_HEIGHT; l++){
         subSumR += Color.red(subSrc[k][l]) * knl[k][l];
         subSumG += Color.green(subSrc[k][l]) * knl[k][l];
         subSumB += Color.blue(subSrc[k][l]) * knl[k][l];
        }
       }

       subSumA = Color.alpha(src.getPixel(i, j));

       if(subSumR <0){
        subSumR = 0;
       }else if(subSumR > 255){
        subSumR = 255;
       }
       
       if(subSumG <0){
        subSumG = 0;
       }else if(subSumG > 255){
        subSumG = 255;
       }
       
       if(subSumB <0){
        subSumB = 0;
       }else if(subSumB > 255){
        subSumB = 255;
       }

       dest.setPixel(i, j, Color.argb(
         subSumA, 
         subSumR, 
         subSumG, 
         subSumB));
      } 
     }
     
     return dest;
    }

}


main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
    <ScrollView 
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        <LinearLayout 
            android:layout_width="fill_parent"
         android:layout_height="fill_parent"
         android:orientation="vertical" >
         <TextView
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:text="Original" />
      <ImageView
          android:id="@+id/imageSource" 
          android:layout_width="wrap_content"
          android:layout_height="wrap_content" 
          android:src="@drawable/testpicture"/>
      <TextView
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:text="Result" />
      <FrameLayout 
          android:layout_width="wrap_content"
          android:layout_height="wrap_content">
          <ImageView
              android:id="@+id/imageAfter" 
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"/>
          <ProgressBar
              android:id="@+id/progressBar"
              style="?android:attr/progressBarStyleLarge"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"/>
      </FrameLayout>
      
     </LinearLayout>
    </ScrollView>

</LinearLayout>


Process image in background thread


Last article "Sharpen image, using Convolution Matrix" perform image processing in main thread (or called UI Thread), system will not respond any other user action before the task finished - It not a good practice! If your activity take long time not responding, it will be killed by the system.



It will be modified to be performed in background Runnable. Before the image process finished, a ProgressBar will be shown to indicate that it's running.



AndroidImageProcessingActivity.java
package com.AndroidImageProcessing;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;

public class AndroidImageProcessingActivity extends Activity {
 
 final static int KERNAL_WIDTH = 3;
 final static int KERNAL_HEIGHT = 3;
 
 int[][] kernalBlur ={
   {0, -1, 0},
   {-1, 5, -1},
   {0, -1, 0}
 };
 
 ImageView imageSource, imageAfter;
 Bitmap bitmap_Source;
 ProgressBar progressBar;
 
 private Handler handler;
 Bitmap afterSharpen;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        imageSource = (ImageView)findViewById(R.id.imageSource);
        imageAfter = (ImageView)findViewById(R.id.imageAfter);
        progressBar = (ProgressBar)findViewById(R.id.progressBar);
        
        bitmap_Source = BitmapFactory.decodeResource(getResources(), R.drawable.testpicture);

        handler = new Handler();
        StratBackgroundProcess();
    }
    
    private void StratBackgroundProcess(){
     
     Runnable runnable = new Runnable(){

   @Override
   public void run() {
    afterSharpen = processingBitmap(bitmap_Source, kernalBlur);
    
    handler.post(new Runnable(){

     @Override
     public void run() {
      progressBar.setVisibility(View.GONE);
      imageAfter.setImageBitmap(afterSharpen);
     }
     
    });
   }
     };
     new Thread(runnable).start();
    }
    
    private Bitmap processingBitmap(Bitmap src, int[][] knl){
     Bitmap dest = Bitmap.createBitmap(
       src.getWidth(), src.getHeight(), src.getConfig());
     
     int bmWidth = src.getWidth();
     int bmHeight = src.getHeight();
     int bmWidth_MINUS_2 = bmWidth - 2;
     int bmHeight_MINUS_2 = bmHeight - 2;
     
     for(int i = 1; i <= bmWidth_MINUS_2; i++){
      for(int j = 1; j <= bmHeight_MINUS_2; j++){
       
       //get the surround 3*3 pixel of current src[i][j] into a matrix subSrc[][]
       int[][] subSrc = new int[KERNAL_WIDTH][KERNAL_HEIGHT];
       for(int k = 0; k < KERNAL_WIDTH; k++){
        for(int l = 0; l < KERNAL_HEIGHT; l++){
         subSrc[k][l] = src.getPixel(i-1+k, j-1+l);
        }
       }
       
       //subSum = subSrc[][] * knl[][]
       int subSumA = 0;
       int subSumR = 0;
       int subSumG = 0;
       int subSumB = 0;

       for(int k = 0; k < KERNAL_WIDTH; k++){
        for(int l = 0; l < KERNAL_HEIGHT; l++){
         subSumA += Color.alpha(subSrc[k][l]) * knl[k][l];
         subSumR += Color.red(subSrc[k][l]) * knl[k][l];
         subSumG += Color.green(subSrc[k][l]) * knl[k][l];
         subSumB += Color.blue(subSrc[k][l]) * knl[k][l];
        }
       }
       
       if(subSumA<0){
        subSumA = 0;
       }else if(subSumA>255){
        subSumA = 255;
       }
       
       if(subSumR<0){
        subSumR = 0;
       }else if(subSumR>255){
        subSumR = 255;
       }
       
       if(subSumG<0){
        subSumG = 0;
       }else if(subSumG>255){
        subSumG = 255;
       }
       
       if(subSumB<0){
        subSumB = 0;
       }else if(subSumB>255){
        subSumB = 255;
       }

       dest.setPixel(i, j, Color.argb(
         subSumA, 
         subSumR, 
         subSumG, 
         subSumB));
      } 
     }
     
     return dest;
    }

}

Modify main.xml to add a ProgressBar overlap imageAfter in a FrameLayout.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
    <ScrollView 
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        <LinearLayout 
            android:layout_width="fill_parent"
         android:layout_height="fill_parent"
         android:orientation="vertical" >
         <TextView
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:text="Original" />
      <ImageView
          android:id="@+id/imageSource" 
          android:layout_width="wrap_content"
          android:layout_height="wrap_content" 
          android:src="@drawable/testpicture"/>
      <TextView
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:text="Result" />
      <FrameLayout 
          android:layout_width="wrap_content"
          android:layout_height="wrap_content">
          <ImageView
              android:id="@+id/imageAfter" 
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"/>
          <ProgressBar
              android:id="@+id/progressBar"
              style="?android:attr/progressBarStyleLarge"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"/>
      </FrameLayout>
      
     </LinearLayout>
    </ScrollView>

</LinearLayout>


Infolinks In Text Ads