Martynas Jurkus
Martynas Jurkus

Reputation: 9301

Android setFocusArea and Auto Focus

I've been battling with this feature for couple of days now...

It seems, that camera is ignoring(?) focus areas that I've defined. Here is snippets of the code:

Focusing:

protected void focusOnTouch(MotionEvent event) {
    if (camera != null) {
        Rect rect = calculateFocusArea(event.getX(), event.getY());

        Parameters parameters = camera.getParameters();
        parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);
        parameters.setFocusAreas(Lists.newArrayList(new Camera.Area(rect, 500)));

        camera.setParameters(parameters);
        camera.autoFocus(this);
    }
}

Focus area calculation:

private Rect calculateFocusArea(float x, float y) {
    int left = clamp(Float.valueOf((x / getSurfaceView().getWidth()) * 2000 - 1000).intValue(), focusAreaSize);
    int top = clamp(Float.valueOf((y / getSurfaceView().getHeight()) * 2000 - 1000).intValue(), focusAreaSize);

    return new Rect(left, top, left + focusAreaSize, top + focusAreaSize);
}

Couple of log events from Camera.AutoFocusCallback#onAutoFocus

Log.d(TAG, String.format("Auto focus success=%s. Focus mode: '%s'. Focused on: %s", focused, camera.getParameters().getFocusMode(), camera.getParameters().getFocusAreas().get(0).rect.toString()));

08-27 11:19:42.240: DEBUG/MyCameraActivity(26268): Auto focus success=true. Focus mode: 'auto'. Focused on: Rect(-109, 643 - -13, 739)
08-27 11:19:55.514: DEBUG/MyCameraActivity(26268): Auto focus success=true. Focus mode: 'auto'. Focused on: Rect(20, 457 - 116, 553)
08-27 11:19:58.037: DEBUG/MyCameraActivity(26268): Auto focus success=true. Focus mode: 'auto'. Focused on: Rect(-159, 536 - -63, 632)
08-27 11:20:00.129: DEBUG/MyCameraActivity(26268): Auto focus success=true. Focus mode: 'auto'. Focused on: Rect(-28, 577 - 68, 673)

Visually it looks like focus succeeds on logged area, but the suddenly it loses focus and focus on center (0, 0), or what takes bigger part of SurfaceView is obtained.

focusAreaSize used in calculation is about 210px (96dp). Testing on HTC One where Camera.getParameters().getMaxNumFocusAreas() is 1.

Initial focus mode (before first tap) is set to FOCUS_MODE_CONTINUOUS_PICTURE.

Am I doing something wrong here? Tinkering with Camera.Area rectangle size or weight doesn't show any noticeable effect.

Upvotes: 41

Views: 59525

Answers (5)

amiron
amiron

Reputation: 731

Hi, try below code copy and change for yourself

public class CameraActivity extends AppCompatActivity implements Camera.AutoFocusCallback {

    private Camera camera;
    private FrameLayout fl_camera_preview;

    ...

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

        setContentView( R.layout.camera_activity );
        //this View, is lens camera 
        fl_camera_preview = findViewById( R.id.fl_camera_preview );
        Button someButtonCapturePicture = findViewById(R.id.someButtonCapturePicture);

        pictureCall = getPictureCallback();

        //check camera access
        if ( getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA) ) {

            if ( safeCameraOpen(0) ) {

                cameraPreview = new CameraPreview( this, camera );
                fl_camera_preview.addView( cameraPreview );

                someButtonCapturePicture.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {

                        camera.takePicture(null, null, pictureCall);

                    }
                });

            } else {
                Log.w(TAG, "getCameraInstance:  Camera is not available (in use or does not exist)." );
            }        

        }

    }

    private boolean safeCameraOpen(int id) {

        boolean qOpened = false;

        try {

            camera = Camera.open( id );
            // set some parameters
            Camera.Parameters par = camera.getParameters();

        List<Camera.Size> supportedPreviewSizes = par.getSupportedPreviewSizes();

            for ( Camera.Size cs : supportedPreviewSizes ) {
              if ( cs.height == 720 ) {
                 par.setPictureSize(cs.width, cs.height);
                 par.setPreviewSize(cs.width, cs.height);
                 break;
              }
            }

            camera.setParameters(par);

            qOpened = ( camera != null );

        } catch (Exception e) {
            Log.e(TAG, "safeCameraOpen: failed to open Camera");
            e.printStackTrace();
        }

        return qOpened;

    }

    public void touchFocusCamera( final Rect touchFocusRect ) {

        //Convert touche coordinate, in width and height to -/+ 1000 range
        final Rect targetFocusRect = new Rect(
                touchFocusRect.left * 2000/fl_camera_preview.getWidth() - 1000,
                touchFocusRect.top * 2000/fl_camera_preview.getHeight() - 1000,
                touchFocusRect.right * 2000/fl_camera_preview.getWidth() - 1000,
                touchFocusRect.bottom * 2000/fl_camera_preview.getHeight() - 1000);

        final List<Camera.Area> focusList = new ArrayList<Camera.Area>();
        Camera.Area focusArea = new Camera.Area(targetFocusRect, 1000);
        focusList.add(focusArea);

        Camera.Parameters para = camera.getParameters();
        List<String> supportedFocusModes = para.getSupportedFocusModes();
        if ( supportedFocusModes != null &&
                supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO) ) {

            try {

                para.setFocusAreas(focusList);
                para.setMeteringAreas(focusList);
                camera.setParameters(para);

                camera.autoFocus( CameraActivity.this );

            } catch (Exception e) {
                Log.e(TAG, "handleFocus: " + e.getMessage() );
            }

        }

    }

    @Override
    public void onAutoFocus(boolean success, Camera camera) {

        if ( success ) {
            camera.cancelAutoFocus();
        }

        float focusDistances[] = new float[3];
        camera.getParameters().getFocusDistances(focusDistances);

    }

    /**
     * Get Bitmap from camera
     * @return picture
     */
    private Camera.PictureCallback getPictureCallback() {

        Camera.PictureCallback picture = new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {

                Log.i(TAG, "onPictureTaken: size bytes photo: " + data.length );

            }
        };

        return picture;

    }

    ...

}

//And SurfaceView with Callback
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

    private static final String TAG = "CameraPreview";
    SurfaceHolder holder;
    Camera camera;

    public CameraPreview( Context context, Camera _camera ) {
        super(context);

        camera = _camera;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        holder = getHolder();
        holder.addCallback(this);

        // deprecated setting, but required on Android versions prior to 3.0
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

        // The Surface has been created, now tell the camera where to draw the preview.
        try {

            camera.setPreviewDisplay(holder);
            camera.startPreview();

        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if( event.getAction() == MotionEvent.ACTION_DOWN ) {

            // Get the pointer's current position
            float x = event.getX();
            float y = event.getY();
            float touchMajor = event.getTouchMajor();
            float touchMinor = event.getTouchMinor();

            Rect touchRect = new Rect(
                    (int)(x - touchMajor/2),
                    (int)(y - touchMinor/2),
                    (int)(x + touchMajor/2),
                    (int)(y + touchMinor/2));

            ((CameraActivity)getContext())
                        .touchFocusCamera( touchRect );

        }

        return true;

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.

        if (this.holder.getSurface() == null) {
            // preview surface does not exist
            return;
        }

        // stop preview before making changes
        try {
            camera.stopPreview();
        } catch (Exception e) {
            // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        try {
            camera.setPreviewDisplay(this.holder);
            camera.startPreview();

        } catch (Exception e) {
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }

    }

    ...

}

Upvotes: 0

zohre
zohre

Reputation: 126

use FOCUS_MODE_FIXED

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
        mCamera = Camera.open(mCameraId);
    } else {
        mCamera = Camera.open();
    }
cameraParams = mCamera.getParameters();
// set the focus mode
cameraParams.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
// set Camera parameters
mCamera.setParameters(cameraParams);

Upvotes: 2

ivan.panasiuk
ivan.panasiuk

Reputation: 1259

Beside setting:

parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);

you need to set:

parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);

if you want real 'live' auto-focus. Also, it will be good to check available focuses:

List<String> focusModes = parameters.getSupportedFocusModes();
LLog.d("focusModes=" + focusModes);
if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE))
    parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);

On Samsung S6 you must set this with little delay (~ 500 ms) after getting camera preview.

Upvotes: 5

Martynas Jurkus
Martynas Jurkus

Reputation: 9301

My problem was much simpler :)

All I had to do is cancel previously called autofocus. Basically the correct order of actions is this:

protected void focusOnTouch(MotionEvent event) {
    if (camera != null) {

        camera.cancelAutoFocus();
        Rect focusRect = calculateTapArea(event.getX(), event.getY(), 1f);
        Rect meteringRect = calculateTapArea(event.getX(), event.getY(), 1.5f);

        Parameters parameters = camera.getParameters();
        parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);
        parameters.setFocusAreas(Lists.newArrayList(new Camera.Area(focusRect, 1000)));

        if (meteringAreaSupported) {
            parameters.setMeteringAreas(Lists.newArrayList(new Camera.Area(meteringRect, 1000)));
        }

        camera.setParameters(parameters);
        camera.autoFocus(this);
    }
}

Update

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    ...
    Parameters p = camera.getParameters();
    if (p.getMaxNumMeteringAreas() > 0) {
       this.meteringAreaSupported = true;
    }
    ...
}

/**
 * Convert touch position x:y to {@link Camera.Area} position -1000:-1000 to 1000:1000.
 */
private Rect calculateTapArea(float x, float y, float coefficient) {
    int areaSize = Float.valueOf(focusAreaSize * coefficient).intValue();

    int left = clamp((int) x - areaSize / 2, 0, getSurfaceView().getWidth() - areaSize);
    int top = clamp((int) y - areaSize / 2, 0, getSurfaceView().getHeight() - areaSize);

    RectF rectF = new RectF(left, top, left + areaSize, top + areaSize);
    matrix.mapRect(rectF);

    return new Rect(Math.round(rectF.left), Math.round(rectF.top), Math.round(rectF.right), Math.round(rectF.bottom));
}

private int clamp(int x, int min, int max) {
    if (x > max) {
        return max;
    }
    if (x < min) {
        return min;
    }
    return x;
}

Upvotes: 54

Grzegorz D.
Grzegorz D.

Reputation: 1806

I had this problem today :/

And after hours of struggling, I found the solution!

It's strange, but it appears that setting focus-mode to "macro" right before setting focus-areas solved the problem ;)

params.setFocusMode(Camera.Parameters.FOCUS_MODE_MACRO);
params.setFocusAreas(focusAreas);
mCamera.setParameters(params);

I have Galaxy S3 with Android 4.1.2

I hope this will work for you either :)

Upvotes: 3

Related Questions