Sunday, March 16, 2014

Try Motion aftereffect on Android

The motion aftereffect (MAE) is a visual illusion experienced after viewing a moving visual stimulus for a time (tens of milliseconds to minutes) with stationary eyes, and then fixating a stationary stimulus. The stationary stimulus appears to move in the opposite direction to the original (physically moving) stimulus. The motion aftereffect is believed to be the result of motion adaptation. ~ Wikipedia.

This exercise TRY to simulate the effect on Android. The code for motion aftereffect generation is modified from http://en.wikipedia.org/wiki/File:Illusion_movie.ogg (c source code), with my optimization. But the effect seem not good! I test it on Nexus 7 tablet.

To see the illusions: Run the app on Android Tablet, and stare to the center of the image. After around one minute, look away (for example look to a face or to your hands). For few seconds everything you see will appear to distort. Or touch the motion picture on screen, it will stop and change to another ImageView.

motion aftereffect (MAE)
Motion AfterEffect (MAE)

The main part is in MySurfaceView.java.
package com.example.androidsurfaceview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MySurfaceView extends SurfaceView {
 
    private SurfaceHolder surfaceHolder;
    private MyThread myThread;
    
    MainActivity mainActivity;
    
    int T;
    static final float freq = 80;

 public MySurfaceView(Context context) {
  super(context);
  init(context);
 }

 public MySurfaceView(Context context, 
   AttributeSet attrs) {
  super(context, attrs);
  init(context);
 }

 public MySurfaceView(Context context, 
   AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  init(context);
 }
 
 private void init(Context c){
  mainActivity = (MainActivity)c;
  T = 0;
  myThread = new MyThread(this);
  
  surfaceHolder = getHolder();

  
  surfaceHolder.addCallback(new SurfaceHolder.Callback(){

   @Override
   public void surfaceCreated(SurfaceHolder holder) {
    myThread.setRunning(true);
    myThread.start();
   }

   @Override
   public void surfaceChanged(SurfaceHolder holder, 
     int format, int width, int height) {
    // TODO Auto-generated method stub
    
   }

   @Override
   public void surfaceDestroyed(SurfaceHolder holder) {
    boolean retry = true;
                myThread.setRunning(false);
                while (retry) {
                       try {
                             myThread.join();
                             retry = false;
                       } catch (InterruptedException e) {
                       }
                }
   }});
 }

 protected void drawSomething(Canvas canvas) {
  
  int sizex = getWidth();
  int sizey = getHeight();

  float divby_sizex_sq = 1.0f/((float)sizex * (float)sizex);

  int[] data = new int[sizex * sizey];
  int m;
  
  T++;
  if(T >= 1200){
   T = 0;
  }
  float halfT = T * 0.5f;

  for (int j=0;j<sizey;j++){
   
   float y0 = j*2-sizey;
   float y2_2 = y0 * y0 * divby_sizex_sq;
   
   float absY = Math.abs(y0/(float)sizey);
            float halfT_plus_absY = absY + halfT;
            float halfT_neg_plus_absY = absY - halfT;
            
            int j_multi_sizex = j*sizex;
   
         for (int i=0;i<sizex;i++){
             float x=(i*2-sizex)/(float)sizex;
             
             //0.2 instead of 0.1 to have a bigger circle
             if ((x*x + y2_2)<0.2){
              m = (int) (Math.sin((halfT_plus_absY+Math.abs(x))*freq)*127.0+128) & 0xFF;
             }else {
              m = (int) (Math.sin((halfT_neg_plus_absY+Math.abs(x))*freq)*127.0+128) & 0xFF;
             }
             
             data[i+j_multi_sizex] = 0xFF000000
               + (m << 16)
               + (m << 8)
               + m;
         }
     }
     
     Bitmap bm = Bitmap.createBitmap(data, sizex, sizey, Bitmap.Config.ARGB_8888);

        canvas.drawBitmap(bm, 0, 0, null);

 }
}

MyThread.java
package com.example.androidsurfaceview;

import android.graphics.Canvas;

public class MyThread extends Thread {
 
 MySurfaceView myView;
 private boolean running = false;

 public MyThread(MySurfaceView view) {
  myView = view;
 }
 
 public void setRunning(boolean run) {
        running = run;
 }

 @Override
 public void run() {
  while(running){
   
   Canvas canvas = myView.getHolder().lockCanvas();
   
   if(canvas != null){
    synchronized (myView.getHolder()) {
     myView.drawSomething(canvas);
    }
    myView.getHolder().unlockCanvasAndPost(canvas);
   }
   
   /*
   try {
    sleep(30);
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
   */
   
  }
 }

}

/res/layout/activity_main.xml, where @drawable/android_er is a 640x480 .png in /res/drawable/ folder.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@android:color/background_dark"
    tools:context="com.example.androidsurfaceview.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" />

    <FrameLayout 
        android:layout_width="640dp"
        android:layout_height="480dp"
        android:layout_gravity="center">
        <com.example.androidsurfaceview.MySurfaceView
            android:id="@+id/myview"
            android:layout_width="640dp"
            android:layout_height="480dp"
            android:layout_gravity="center" />
        <ImageView 
            android:id="@+id/imageicon"
            android:layout_width="640dp"
            android:layout_height="480dp"
            android:layout_gravity="center"
            android:src="@drawable/android_er"
            android:visibility="invisible"/>
    </FrameLayout>
    
</LinearLayout>

MainActivity.java. The Motion graph will run when the app start. When user touch on it, it will show the ImageView of "@drawable/android_er".
package com.example.androidsurfaceview;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;

public class MainActivity extends Activity {
 
 ImageView imageIcon;
 MySurfaceView mySurfaceView;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  mySurfaceView = (MySurfaceView)findViewById(R.id.myview);
  imageIcon = (ImageView)findViewById(R.id.imageicon);
  
  mySurfaceView.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    imageIcon.setVisibility(View.VISIBLE);
    mySurfaceView.setVisibility(View.INVISIBLE);
   }});

 }

}

Modify AndroidManifest.xml to force the app run in landscape mode.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidsurfaceview"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.androidsurfaceview.MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="landscape" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>



download filesDownload the files.

Download and try the APK.

No comments: