Sunday, October 27, 2013

Blur bitmap

This exercise generate a blur bitmap by changing each pixel to the average of its surrounding 5x5 pixel.

Blur bitmap


package com.example.androiddrawbitmap;

import java.io.FileNotFoundException;

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

public class MainActivity extends Activity {

 Button btnLoadImage;
 ImageView imageResult, imageOriginal;
 TextView textDur;

 final int RQS_IMAGE1 = 1;

 Uri source;
 Bitmap bitmapMaster;
 Canvas canvasMaster;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  btnLoadImage = (Button) findViewById(R.id.loadimage);
  imageResult = (ImageView) findViewById(R.id.result);
  imageOriginal = (ImageView)findViewById(R.id.original);
  textDur = (TextView)findViewById(R.id.dur);

  btnLoadImage.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);
   }
  });
 }

 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);

  if (resultCode == RESULT_OK) {
   switch (requestCode) {
   case RQS_IMAGE1:
    source = data.getData();

    try {
     bitmapMaster = BitmapFactory
       .decodeStream(getContentResolver().openInputStream(
         source));
     
     imageOriginal.setImageBitmap(bitmapMaster);
     loadBlurBitmap(bitmapMaster);

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

    break;
   }
  }
 }

 private void loadBlurBitmap(Bitmap src) {
  if (src != null) {
   
   Bitmap bmBlur;
   
   long startTime = System.currentTimeMillis();
   bmBlur = getBlurBitmap(src);
   long duration = System.currentTimeMillis() - startTime;
   
   imageResult.setImageBitmap(bmBlur);
   textDur.setText("processing duration(ms): " + duration);
  }
 }

 private Bitmap getBlurBitmap(Bitmap src) {
  
  final int widthKernal = 5;
  final int heightKernal = 5;

  int w = src.getWidth();
  int h = src.getHeight();

  Bitmap blurBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);

  for (int x = 0; x < w; x++) {
   for (int y = 0; y < h; y++) {

    int r = 0;
    int g = 0;
    int b = 0;
    int a = 0;
    
    for (int xk = 0; xk < widthKernal; xk++) {
     for (int yk = 0; yk < heightKernal; yk++) {
      int px = x + xk -2;
      int py = y + yk -2;

      if(px < 0){
       px = 0;
      }else if(px >= w){
       px = w-1;
      }
      
      if(py < 0){
       py = 0;
      }else if(py >= h){
       py = h-1;
      }
      
      int intColor = src.getPixel(px, py);
      r += Color.red(intColor);
      g += Color.green(intColor);
      b += Color.blue(intColor);
      a += Color.alpha(intColor);
      
     }
    }
    
    blurBitmap.setPixel(x, y, Color.argb(a/25, r/25, g/25, b/25));

   }
  }

  return blurBitmap;
 }

}


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/background_dark"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <Button
        android:id="@+id/loadimage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load Image 1" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >
            <ImageView
                android:id="@+id/original"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:adjustViewBounds="true"
                android:scaleType="fitXY" />
            <ImageView
                android:id="@+id/result"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:adjustViewBounds="true"
                android:scaleType="fitXY" />
            <TextView 
                android:id="@+id/dur"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
        </LinearLayout>
    </ScrollView>
</LinearLayout>

download filesDownload the files.

download filesDownload and try the APK.

GridView example: load images to GridView from SD Card in background

Recall from the "old exercise of GridView", loading images to GridView from SD Card in onCreate() method running in main thread. Actually, it may take long time to finish, so it should be moved to background thread. This exercise move the file loading operation to AsyncTask running in background thread.

Also introduce Reload button to demonstrate how to clear and reload the list.

Load images to GridView from SD Card


Here are some notes in my implementation:
  • In my trial experience, should not access ImageAdapter(extends BaseAdapter) from background thread such as doInBackground(). Doing so will conflict with ImageAdapter's getView() method, and generate IndexOutOfBoundsException occasionally. It whould be accessed in UI thread, so the clear(), add() and notifyDataSetChanged() have been moved to onPreExecute(), onProgressUpdate() and onPostExecute().
  • Cancel the AsyncTask if not need.
  • In my approach, always new another ImageAdapter when reloading; to prevent the list inside mixed with a invalid but still running AsyncTask.

package com.example.androidgridview;

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

import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends Activity {
 
 AsyncTaskLoadFiles myAsyncTaskLoadFiles;

 public class AsyncTaskLoadFiles extends AsyncTask<Void, String, Void> {
  
  File targetDirector;
  ImageAdapter myTaskAdapter;

  public AsyncTaskLoadFiles(ImageAdapter adapter) {
   myTaskAdapter = adapter;
  }

  @Override
  protected void onPreExecute() {
   String ExternalStorageDirectoryPath = Environment
     .getExternalStorageDirectory().getAbsolutePath();

   String targetPath = ExternalStorageDirectoryPath + "/test/";
   targetDirector = new File(targetPath);
   myTaskAdapter.clear();
   
   super.onPreExecute();
  }

  @Override
  protected Void doInBackground(Void... params) {
   
   File[] files = targetDirector.listFiles();
   for (File file : files) {
    publishProgress(file.getAbsolutePath());
    if (isCancelled()) break;
   }
   return null;
  }

  @Override
  protected void onProgressUpdate(String... values) {
   myTaskAdapter.add(values[0]);
   super.onProgressUpdate(values);
  }

  @Override
  protected void onPostExecute(Void result) {
   myTaskAdapter.notifyDataSetChanged();
   super.onPostExecute(result);
  }

 }

 public class ImageAdapter extends BaseAdapter {

  private Context mContext;
  ArrayList<String> itemList = new ArrayList<String>();

  public ImageAdapter(Context c) {
   mContext = c;
  }

  void add(String path) {
   itemList.add(path);
  }
  
  void clear() {
   itemList.clear();
  }
  
  void remove(int index){
   itemList.remove(index);
  }

  @Override
  public int getCount() {
   return itemList.size();
  }

  @Override
  public Object getItem(int position) {
   // TODO Auto-generated method stub
   return itemList.get(position);
  }

  @Override
  public long getItemId(int position) {
   // TODO Auto-generated method stub
   return 0;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   ImageView imageView;
   if (convertView == null) { // if it's not recycled, initialize some
          // attributes
    imageView = new ImageView(mContext);
    imageView.setLayoutParams(new GridView.LayoutParams(220, 220));
    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    imageView.setPadding(8, 8, 8, 8);
   } else {
    imageView = (ImageView) convertView;
   }

   Bitmap bm = decodeSampledBitmapFromUri(itemList.get(position), 220,
     220);

   imageView.setImageBitmap(bm);
   return imageView;
  }

  public Bitmap decodeSampledBitmapFromUri(String path, int reqWidth,
    int reqHeight) {

   Bitmap bm = null;
   // First decode with inJustDecodeBounds=true to check dimensions
   final BitmapFactory.Options options = new BitmapFactory.Options();
   options.inJustDecodeBounds = true;
   BitmapFactory.decodeFile(path, options);

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

   // Decode bitmap with inSampleSize set
   options.inJustDecodeBounds = false;
   bm = BitmapFactory.decodeFile(path, 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) {
    if (width > height) {
     inSampleSize = Math.round((float) height
       / (float) reqHeight);
    } else {
     inSampleSize = Math.round((float) width / (float) reqWidth);
    }
   }

   return inSampleSize;
  }

 }

 ImageAdapter myImageAdapter;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  final GridView gridview = (GridView) findViewById(R.id.gridview);
  myImageAdapter = new ImageAdapter(this);
  gridview.setAdapter(myImageAdapter);

  /*
   * Move to asyncTaskLoadFiles String ExternalStorageDirectoryPath =
   * Environment .getExternalStorageDirectory() .getAbsolutePath();
   * 
   * String targetPath = ExternalStorageDirectoryPath + "/test/";
   * 
   * Toast.makeText(getApplicationContext(), targetPath,
   * Toast.LENGTH_LONG).show(); File targetDirector = new
   * File(targetPath);
   * 
   * File[] files = targetDirector.listFiles(); for (File file : files){
   * myImageAdapter.add(file.getAbsolutePath()); }
   */
  myAsyncTaskLoadFiles = new AsyncTaskLoadFiles(myImageAdapter);
  myAsyncTaskLoadFiles.execute();

  gridview.setOnItemClickListener(myOnItemClickListener);
  
  Button buttonReload = (Button)findViewById(R.id.reload);
  buttonReload.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    
    //Cancel the previous running task, if exist.
    myAsyncTaskLoadFiles.cancel(true);
    
    //new another ImageAdapter, to prevent the adapter have
    //mixed files
    myImageAdapter = new ImageAdapter(MainActivity.this);
    gridview.setAdapter(myImageAdapter);
    myAsyncTaskLoadFiles = new AsyncTaskLoadFiles(myImageAdapter);
    myAsyncTaskLoadFiles.execute();
   }});

 }

 OnItemClickListener myOnItemClickListener = new OnItemClickListener() {

  @Override
  public void onItemClick(AdapterView<?> parent, View view, int position,
    long id) {
   String prompt = "remove " + (String) parent.getItemAtPosition(position);
   Toast.makeText(getApplicationContext(), prompt, Toast.LENGTH_SHORT)
     .show();
   
   myImageAdapter.remove(position);
   myImageAdapter.notifyDataSetChanged();

  }
 };

}


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="vertical">

    <Button
        android:id="@+id/reload"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Reload"/>
    <GridView
        android:id="@+id/gridview"
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent"
        android:columnWidth="90dp"
        android:numColumns="auto_fit"
        android:verticalSpacing="10dp"
        android:horizontalSpacing="10dp"
        android:stretchMode="columnWidth"
        android:gravity="center"/>

</LinearLayout>


download filesDownload the files.

download filesDownload and try the APK.

Example to apply BlurMaskFilter on Bitmap

apply BlurMaskFilter on Bitmap




package com.example.androiddrawbitmap;

import java.io.FileNotFoundException;

import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends Activity {

 Button btnLoadImage;
 ImageView imageResult;

 final int RQS_IMAGE1 = 1;

 Uri source;
 Bitmap bitmapMaster;
 Canvas canvasMaster;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  btnLoadImage = (Button) findViewById(R.id.loadimage);
  imageResult = (ImageView) findViewById(R.id.result);

  btnLoadImage.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);
   }
  });
 }

 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);

  if (resultCode == RESULT_OK) {
   switch (requestCode) {
   case RQS_IMAGE1:
    source = data.getData();

    try {
     bitmapMaster = BitmapFactory
       .decodeStream(getContentResolver().openInputStream(
         source));
     loadGrayBitmap(bitmapMaster);

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

    break;
   }
  }
 }

 private void loadGrayBitmap(Bitmap src) {
  if (src != null) {

   int w = src.getWidth();
   int h = src.getHeight();
   RectF rectF = new RectF(w/4, h/4, w*3/4, h*3/4);
   float blurRadius = 100.0f;
   
   Bitmap bitmapResult = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
   Canvas canvasResult = new Canvas(bitmapResult);
         
         Paint blurPaintOuter = new Paint();
         blurPaintOuter.setColor(0xFFffffff);
         blurPaintOuter.setMaskFilter(new 
           BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.INNER));
         canvasResult.drawBitmap(bitmapMaster, 0, 0, blurPaintOuter);
         
         Paint blurPaintInner = new Paint();
         blurPaintInner.setColor(0xFFffffff);
         blurPaintInner.setMaskFilter(new 
           BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.OUTER));
         canvasResult.drawRect(rectF, blurPaintInner);

   imageResult.setImageBitmap(bitmapResult);
  }
 }
}


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:background="@android:color/background_dark"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <Button
        android:id="@+id/loadimage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load Image 1" />
    
    <ImageView
                android:id="@+id/result"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:adjustViewBounds="true"
                android:scaleType="centerInside" />

</LinearLayout>


download filesDownload the files.

Learn how to use the Android NDK, by creating an image processing application

The Android Software Developer Kit (SDK) used by the majority of Android application developers requires the use of the Java™ programming language. However, there is a large body of C language code available online. The Android Native Developer Kit (NDK) permits an Android developer to reuse existing C source code within an Android application. The tutorial,Reuse existing C code with the Android NDK by IBM 12 Apr 2011, introduce how to create an image processing application in the Java programming language that uses C code to perform basic image processing operations.

The tutorial is available in PDF format (773 KB | 42 pages) also.

Reuse existing C code with the Android NDK
Reuse existing C code with the Android NDK

Android Design for UI Developers

Google I/O 2013 tutorial, Android Design for UI Developers:

In this tutorial video, will explore the arsenal of tools available to Android UI engineers that let developers implement some of these important guidelines, including responsive design with multi-pane layouts, metrics and layout grids, and core navigation components.

Convert Bitmap to gray-scale with ColorMatrix

To convert Bitmap to gray-scale, we can apply ColorMatrix with the following matrix:

   //Array to generate Gray-Scale image
   float[] GrayArray = {
     0.213f, 0.715f, 0.072f, 0.0f, 0.0f,
     0.213f, 0.715f, 0.072f, 0.0f, 0.0f,
     0.213f, 0.715f, 0.072f, 0.0f, 0.0f,
     0.0f, 0.0f, 0.0f, 1.0f, 0.0f,  
   };

Convert Bitmap to gray-scale with ColorMatrix


package com.example.androiddrawbitmap;

import java.io.FileNotFoundException;

import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends Activity {

 Button btnLoadImage;
 ImageView imageResult;

 final int RQS_IMAGE1 = 1;

 Uri source;
 Bitmap bitmapMaster;
 Canvas canvasMaster;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  btnLoadImage = (Button) findViewById(R.id.loadimage);
  imageResult = (ImageView) findViewById(R.id.result);

  btnLoadImage.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);
   }
  });

 }

 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);

  if (resultCode == RESULT_OK) {
   switch (requestCode) {
   case RQS_IMAGE1:
    source = data.getData();

    try {
     bitmapMaster = BitmapFactory
       .decodeStream(getContentResolver().openInputStream(
         source));
     loadGrayBitmap(bitmapMaster);

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

    break;
   }
  }
 }

 private void loadGrayBitmap(Bitmap src) {
  if (src != null) {
   
   //Array to generate Identity image
   float[] IdentityArray = {
     1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
     0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
     0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
     0.0f, 0.0f, 0.0f, 1.0f, 0.0f,  
   };
   
   //Array to generate Gray-Scale image
   float[] GrayArray = {
     0.213f, 0.715f, 0.072f, 0.0f, 0.0f,
     0.213f, 0.715f, 0.072f, 0.0f, 0.0f,
     0.213f, 0.715f, 0.072f, 0.0f, 0.0f,
     0.0f, 0.0f, 0.0f, 1.0f, 0.0f,  
   };
   
   ColorMatrix colorMatrixGray = new ColorMatrix(GrayArray);

   int w = src.getWidth();
   int h = src.getHeight();
   
   Bitmap bitmapResult = Bitmap
     .createBitmap(w, h, Bitmap.Config.ARGB_8888);
   Canvas canvasResult = new Canvas(bitmapResult);
   Paint paint = new Paint();
   
   ColorMatrixColorFilter filter = new ColorMatrixColorFilter(colorMatrixGray);
   paint.setColorFilter(filter);
   canvasResult.drawBitmap(src, 0, 0, paint);

   imageResult.setImageBitmap(bitmapResult);
  }
 }

}


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <Button
        android:id="@+id/loadimage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load Image 1" />
    
    <ImageView
                android:id="@+id/result"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:adjustViewBounds="true"
                android:background="@android:color/background_dark"
                android:scaleType="centerInside" />

</LinearLayout>


download filesDownload the files.

download filesDownload and try the APK.

Remark: This exercise aim to demonstrate the matrix. Actually you can setSaturation(0) of  ColorMatrix to do the same effect.

Enable Developer Options for devices with Android 4.2 and higher

On devices running Android 4.2 and higher, the Developer options is hidden by default. To enable Developer Options, go to Settings > About phone and tap Build number seven times, the detail steps may various depends on model.

Here is the steps to enable Developer Options on HTC One X running Android 4.2.2:

- Open Settings, select About.


- Open Software information


- Open More


- Tap on Build number 7 times


You are now a developer!
The Developer Options will be in Settings.