Jeff Bootsholz
Jeff Bootsholz

Reputation: 3068

Android Camera Focus Mode

I am trying to make a custom camera application I want to let the users can choose the focus mode in this application.

The focus mode is auto and touch-to-focus

If we want to use touch-to-focus in the camera , how can be start with?

Upvotes: 15

Views: 35812

Answers (5)

Ken
Ken

Reputation: 1393

Call this to enable Touch-To-Focus mode:

private void setTouchToFocusMode(Camera.Parameters parameters){
    String focusMode;
    if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
        focusMode = Camera.Parameters.FOCUS_MODE_AUTO;
    }
    if (focusMode != null && focusMode.length() > 0){
        parameters.setFocusMode(focusMode);
    }
}

When user taps on screen, call this below to set focus area:

private static final int FOCUS_WIDTH = 80;
private static final int FOCUS_HEIGHT = 80;

public static String setFocalPoint(Camera.Parameters params, int x, int y){
    String focusMode = "";
    if (params != null && params.getMaxNumFocusAreas() > 0) {
        List<Camera.Area> focusArea = new ArrayList<Camera.Area>();
        focusArea.add(new Camera.Area(new Rect(x, y, x + FOCUS_WIDTH, y + FOCUS_HEIGHT), 1000));

        params.setFocusAreas(focusArea);

        if(params.getMaxNumMeteringAreas() > 0) {
            params.setMeteringAreas(focusArea);
        }
        if(params.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
            params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
            focusMode = Camera.Parameters.FOCUS_MODE_AUTO;
        }
    }
    return focusMode;
}

Call autoFocus/cancelAutoFocus for action:

mCamera.cancelAutoFocus();    
mCamera.autoFocus(mAutoFocusCallback);

Upvotes: 0

Jaydipsinh Zala
Jaydipsinh Zala

Reputation: 16798

I was trying to implement focus functionality in my app and achieved this functionality in the way i wanted. To implement Touch to Focus please refer the code below.

CameraPreview.java

public class CameraPreview extends SurfaceView implements
    SurfaceHolder.Callback {
private SurfaceHolder mSurfaceHolder;
private Camera mCamera;
private OnFocusListener onFocusListener;

private boolean needToTakePic = false;

private Camera.AutoFocusCallback myAutoFocusCallback = new Camera.AutoFocusCallback() {

    @Override
    public void onAutoFocus(boolean arg0, Camera arg1) {
        if (arg0) {
            mCamera.cancelAutoFocus();
        }
    }
};

// Constructor that obtains context and camera
@SuppressWarnings("deprecation")
public CameraPreview(Context context, Camera camera) {
    super(context);
    this.mCamera = camera;
    this.mSurfaceHolder = this.getHolder();
    this.mSurfaceHolder.addCallback(this);
    this.mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    this.onFocusListener = (OnFocusListener) context;
}

@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
    try {
        mCamera.setPreviewDisplay(surfaceHolder);
        mCamera.getParameters().setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        mCamera.setDisplayOrientation(90);
        mCamera.startPreview();
    } catch (IOException e) {
        // left blank for now
        e.printStackTrace();
    }
}

@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
    mCamera.stopPreview();
    this.mSurfaceHolder.removeCallback(this);
    mCamera.release();
}

@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format,
                           int width, int height) {
    // start preview with new settings
    try {
        mCamera.setPreviewDisplay(surfaceHolder);
        mCamera.startPreview();
    } catch (Exception e) {
        // intentionally left blank for a test
        e.printStackTrace();
    }
}

/**
 * Called from PreviewSurfaceView to set touch focus.
 *
 * @param - Rect - new area for auto focus
 */
public void doTouchFocus(final Rect tfocusRect) {
    try {
        List<Camera.Area> focusList = new ArrayList<Camera.Area>();
        Camera.Area focusArea = new Camera.Area(tfocusRect, 1000);
        focusList.add(focusArea);

        Camera.Parameters param = mCamera.getParameters();
        param.setFocusAreas(focusList);
        param.setMeteringAreas(focusList);
        mCamera.setParameters(param);

        mCamera.autoFocus(myAutoFocusCallback);
    } catch (Exception e) {
        e.printStackTrace();
    }

    if (isNeedToTakePic()) {
        onFocusListener.onFocused();
    }
}

@Override
public boolean onTouchEvent(MotionEvent event) {

    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        float x = event.getX();
        float y = event.getY();

        Rect touchRect = new Rect(
                (int) (x - 100),
                (int) (y - 100),
                (int) (x + 100),
                (int) (y + 100));


        final Rect targetFocusRect = new Rect(
                touchRect.left * 2000 / this.getWidth() - 1000,
                touchRect.top * 2000 / this.getHeight() - 1000,
                touchRect.right * 2000 / this.getWidth() - 1000,
                touchRect.bottom * 2000 / this.getHeight() - 1000);

        doTouchFocus(targetFocusRect);
    }

    return false;
}

public boolean isNeedToTakePic() {
    return needToTakePic;
}

public void setNeedToTakePic(boolean needToTakePic) {
    this.needToTakePic = needToTakePic;
}
}

MainActivity.java

public class MainActivity extends Activity
    implements OnFocusListener {
private Button captureButton, switchCameraButton;
private Camera mCamera;
private CameraPreview mCameraPreview;
private int currentCameraId;

/**
 * Called when the activity is first created.
 */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if (getIntent().hasExtra("camera_id")) {
        currentCameraId = getIntent().getIntExtra("camera_id", Camera.CameraInfo.CAMERA_FACING_BACK);
    } else {
        currentCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
    }

    captureButton = (Button) findViewById(R.id.button_capture);
    captureButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(final View v) {
            // Obtain MotionEvent object
            v.setEnabled(false);
            mCameraPreview.setNeedToTakePic(true);
            long downTime = SystemClock.uptimeMillis();
            long eventTime = SystemClock.uptimeMillis() + 100;
            float x = mCameraPreview.getWidth() / 2;
            float y = mCameraPreview.getHeight() / 2;
            // List of meta states found here:     developer.android.com/reference/android/view/KeyEvent.html#getMetaState()
            int metaState = 0;
            MotionEvent motionEvent = MotionEvent.obtain(
                    downTime,
                    eventTime,
                    MotionEvent.ACTION_DOWN,
                    x,
                    y,
                    metaState
            );

            // Dispatch touch event to view
            mCameraPreview.dispatchTouchEvent(motionEvent);
        }
    });

    switchCameraButton = (Button) findViewById(R.id.button_switch_camera);
    switchCameraButton.setVisibility(
            Camera.getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE);
    switchCameraButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mCamera.stopPreview();
            //NB: if you don't release the current camera before switching, you app will crash
            mCameraPreview.getHolder().removeCallback(mCameraPreview);
            mCamera.release();

            //swap the id of the camera to be used
            if (currentCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
                currentCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
            } else {
                currentCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
            }

            mCamera = getCameraInstance(currentCameraId);
            mCameraPreview = new CameraPreview(MainActivity.this, mCamera);
            FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
            preview.removeAllViews();
            preview.addView(mCameraPreview);
        }
    });
}

@Override
protected void onResume() {
    super.onResume();
    mCamera = getCameraInstance(currentCameraId);
    mCameraPreview = new CameraPreview(this, mCamera);
    FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
    preview.addView(mCameraPreview);
}

/**
 * Helper method to access the camera returns null if it cannot get the
 * camera or does not exist
 *
 * @return
 */
private Camera getCameraInstance(int currentCameraId) {
    Camera camera = null;
    try {
        camera = Camera.open(currentCameraId);
    } catch (Exception e) {
        // cannot get camera or does not exist
    }
    return camera;
}

Camera.PictureCallback mPicture = new Camera.PictureCallback() {
    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
        File pictureFile = getOutputMediaFile();
        if (pictureFile == null) {
            return;
        }
        try {
            FileOutputStream fos = new FileOutputStream(pictureFile);
            fos.write(data);
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
};

private static File getOutputMediaFile() {
    File mediaStorageDir = new File(
            Environment
                    .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
            "MyCameraApp");
    if (!mediaStorageDir.exists()) {
        if (!mediaStorageDir.mkdirs()) {
            Log.d("MyCameraApp", "failed to create directory");
            return null;
        }
    }
    // Create a media file name
    //        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
    //                .format(new Date());
    File mediaFile;
    mediaFile = new File(mediaStorageDir.getPath() + File.separator
            + "IMG_" + "DEMO_" + ".jpg");
    if (mediaFile.exists()) mediaFile.delete();

    return mediaFile;
}

@Override
public void onFocused() {
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            mCamera.takePicture(null, null, mPicture);
            mCameraPreview.setNeedToTakePic(false);
            captureButton.setEnabled(true);
        }
    }, 1500);
}
}

activity_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">

<FrameLayout
    android:id="@+id/camera_preview"
    android:layout_width="fill_parent"
    android:layout_height="0dp"
    android:layout_weight="1" />

<Button
    android:id="@+id/button_switch_camera"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:text="Switch Camera" />

<Button
    android:id="@+id/button_capture"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:text="Capture" />

</LinearLayout>

You can find sample app on Github - Custom Camera App

Upvotes: 2

superUser
superUser

Reputation: 1032

Try this:

public void takePhoto(File photoFile, String workerName, int width, int height, int    quality) {
if (getAutoFocusStatus()){
    camera.autoFocus(new AutoFocusCallback() {
        @Override
        public void onAutoFocus(boolean success, Camera camera) {
            camera.takePicture(shutterCallback, rawCallback, jpegCallback);
        }
    }); 
}else{
    camera.takePicture(shutterCallback, rawCallback, jpegCallback);
}

However, I've also seen this to work, possibly more accurately:

if (getAutoFocusStatus()){
    camera.autoFocus(new AutoFocusCallback() {
        @Override
        public void onAutoFocus(boolean success, Camera camera) {
           if(success) camera.takePicture(shutterCallback, rawCallback, jpegCallback);
        }
    }); 
}else{
    camera.takePicture(shutterCallback, rawCallback, jpegCallback);
}

The last one takes the picture at the moment the focussing is successfully completed. It works very well for using with QR scanning codes. I believe the same applies to cases like this.

Upvotes: 8

Jeff Bootsholz
Jeff Bootsholz

Reputation: 3068

It is already implemented buthow to modify this if I want to add touch to focus?

public void takePhoto(File photoFile, String workerName, int width, int height, int    quality) {
    if (getAutoFocusStatus()){
        camera.autoFocus(new AutoFocusCallback() {
            @Override
            public void onAutoFocus(boolean success, Camera camera) {
                camera.takePicture(shutterCallback, rawCallback, jpegCallback);
            }
        }); 
    }else{
        camera.takePicture(shutterCallback, rawCallback, jpegCallback);
    }

    this.photoFile = photoFile;
    this.workerName = workerName;
    this.imageOutputWidth = width;
    this.imageOutputHeight = height;
}

public void takePhoto(File photoFile, int width, int height, int quality) {
    takePhoto(photoFile, null, width, height, quality);
}

Upvotes: 5

Ziteng Chen
Ziteng Chen

Reputation: 1969

The feature is software/hardware/manufacture dependent, my suggestion is that you first find a phone like Galaxy Nexus flashed with Android 4.x, then try the android.hardware.Camera.Parameters.getMaxNumFocusAreas() on it, if the return value is greater than zero then you are lucky, and can then use the setFocusAreas() to implement your "touch to focus" feature.

Why:

In old Android versions there is no public API to set the focus areas. Although many manufacturers managed to create their own API and implementation, they won't share.

Android introduced the focus areas API in API level 14, however the phone manufacturers may choose not to implement it (i.e. choose to stick to their own solutions). To check if the API is supported you can call getMaxNumFocusAreasa() first, if it returns a positive integer that means the phone does implement the API and you can go on enabling the "touch focus" feature in your camera app. (The API is also one of the enablers of the "face detection" feature, when faces are recognized the camera app uses the API to let the camera do auto focus on the them.)

You may refer to the vanilla Android Camera app source code for how to use the API properly.

References:

  1. Android Camera API

getMaxNumFocusAreas()

setFocusAreas()

  1. Android 4.0 Camera app source code

mInitialParams.getMaxNumFocusAreas()

mParameters.setFocusAreas()

Regards

Ziteng Chen

Upvotes: 17

Related Questions