Euridice01
Euridice01

Reputation: 2568

How to implement visual indicator when camera is focused

I want to display a basic circle when the user focuses manually (tap to focus) for the camera page in my app.

I already have autofocus implemented below on tap but I'm not sure how to draw the circle on focus and dismiss it when the view becomes unfocused as well keep redrawing it when the camera is focused. The indicator should not be part of the final photo, just as a guide to the user when the camera is focused or not.

Here's what I have so far:

public class AutoFocusCallback : Java.Lang.Object, IAutoFocusCallback
{
    public void OnAutoFocus(bool success, Android.Hardware.Camera camera)
    {
        var parameters = camera.GetParameters();
        var supportedFocusModes = parameters.SupportedFocusModes;

        if (string.IsNullOrEmpty(parameters.FocusMode))
        {
            string focusModeContinuous = Android.Hardware.Camera.Parameters.FocusModeContinuousPicture;
            string focusModeAuto = Android.Hardware.Camera.Parameters.FocusModeAuto;

            if (supportedFocusModes != null && supportedFocusModes.Any())
            {
                if (supportedFocusModes.Contains(focusModeContinuous))
                {
                    parameters.FocusMode = focusModeContinuous;
                }
                else if (supportedFocusModes.Contains(focusModeAuto))
                {
                    parameters.FocusMode = focusModeAuto;
                }
            }
        }

        if (supportedFocusModes != null && supportedFocusModes.Any())
        {
            if (parameters.MaxNumFocusAreas > 0)
            {
                parameters.FocusAreas = null; 
            }

             if (success)
             {
                CameraPage cameraPage = new CameraPage();
                Canvas canvas = new Canvas(); 
                cameraPage.Draw(canvas); 
            }
            camera.SetParameters(parameters);
            camera.StartPreview();
        }
    }
}

public bool OnTouch(Android.Views.View v, MotionEvent e)
{
    if (camera != null)
    {
        var parameters = camera.GetParameters();
        camera.CancelAutoFocus();
        Rect focusRect = CalculateTapArea(e.GetX(), e.GetY(), 1f);

        if (parameters.FocusMode != Android.Hardware.Camera.Parameters.FocusModeAuto)
        {
            parameters.FocusMode = Android.Hardware.Camera.Parameters.FocusModeAuto;
        }
        if (parameters.MaxNumFocusAreas > 0)
        {
            List<Area> mylist = new List<Area>();
            mylist.Add(new Android.Hardware.Camera.Area(focusRect, 1000));
            parameters.FocusAreas = mylist;
        }

        try
        {
            camera.CancelAutoFocus();
            camera.SetParameters(parameters);
            camera.StartPreview();
            camera.AutoFocus(new AutoFocusCallback());
        }
        catch (System.Exception ex)
        {
            Console.WriteLine(ex.ToString());
            Console.Write(ex.StackTrace);
        }
        return true;
    }
    else
    {
        return false;
    } 
}

private Rect CalculateTapArea(object x, object y, float coefficient)
{
    var focusAreaSize = Math.Max(textureView.Width, textureView.Height) / 8; //Recommended focus area size from the manufacture is 1/8 of the image
    int areaSize = focusAreaSize * (int)coefficient;

    int left = clamp(Convert.ToInt32(x) - areaSize / 2, 0, textureView.Width - areaSize);
    int top = clamp(Convert.ToInt32(y) - areaSize / 2, 0, textureView.Height - areaSize);

    RectF rectF = new RectF(left, top, left + areaSize, top + areaSize);
    Matrix.MapRect(rectF);

    return new Rect((int)System.Math.Round(rectF.Left), (int)System.Math.Round(rectF.Top), (int)System.Math.Round(rectF.Right), (int)System.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;
}

public override void Draw(Canvas canvas)
{
    base.Draw(canvas);
    Paint p = new Paint();
    p.Color = Android.Graphics.Color.White; 
    p.SetStyle(Paint.Style.Stroke);
    p.StrokeWidth = 3; 
    canvas.DrawCircle(300, 300, 100, p);
}

E.g. like in this picture:

enter image description here

This is for my Xamarin.Forms Android app.

EDIT: Here's a copy of the Android Source code for the camera but not sure where they're making the indicator:

https://android.googlesource.com/platform/packages/apps/Camera.git/+/refs/heads/marshmallow-release/src/com/android/camera

Similar SO posts with no official answers:

How to implement tap to focus indicator in android camera?

Implement indicator on tap to focus in camera [android]

Android Camera2 - Draw circle focus area

As you can see, multiple people have the same question but no answers! Please let me know if I'm still being unclear?

Upvotes: 3

Views: 1873

Answers (1)

Grace Feng
Grace Feng

Reputation: 16652

I already have autofocus implemented below on tap but I'm not sure how to draw the circle on focus and dismiss it when the view becomes unfocused as well keep redrawing it when the camera is focused.

The picture you posted looks like a system camera to me, if using Intent to launch the system camera, I'm not sure it is possible to add a focus circle on the system's camera view. But I guess you possibly used a SurfaceView or TextureView to host camera by yourself.

If so, for your scenario, I think the most simply method is to place a ImageView in your layout and change its Visibility and reset its LayoutParameters according to the camera focus state. The image source need to be a transparent one for example I used this one. Then my layout is like so:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
  <TextureView android:id="@+id/textureView"
               android:layout_height="match_parent"
               android:layout_width="match_parent" />

  <Button android:id="@+id/take_photo"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="@string/takephoto" />

  <ImageView android:id="@+id/focuscircle"
             android:layout_height="80dp"
             android:layout_width="80dp"
             android:layout_centerInParent="true"
             android:src="@drawable/FocusCircle"
             android:visibility="invisible" />
</RelativeLayout>

I've modified your code a little bit in order to make it meet my scenario using TextureView. here is the code to make the image visible when tap the screen:

private void _textureView_Touch(object sender, View.TouchEventArgs e)
{
    if (_camera != null)
    {
        var parameters = _camera.GetParameters();
        _camera.CancelAutoFocus();
        Rect focusRect = CalculateTapArea(e.Event.GetX(), e.Event.GetY(), 1f);

        if (parameters.FocusMode != Android.Hardware.Camera.Parameters.FocusModeAuto)
        {
            parameters.FocusMode = Android.Hardware.Camera.Parameters.FocusModeAuto;
        }
        if (parameters.MaxNumFocusAreas > 0)
        {
            List<Area> mylist = new List<Area>();
            mylist.Add(new Android.Hardware.Camera.Area(focusRect, 1000));
            parameters.FocusAreas = mylist;
        }

        try
        {
            _camera.CancelAutoFocus();
            _camera.SetParameters(parameters);
            _camera.StartPreview();
            _camera.AutoFocus(new AutoFocusCallBack());

            MarginLayoutParams margin = new MarginLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WrapContent,
            ViewGroup.LayoutParams.WrapContent));
            margin.SetMargins(focusRect.Left, focusRect.Top,
                focusRect.Right, focusRect.Bottom);
            RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(margin);
            layoutParams.Height = 200;
            layoutParams.Width = 200;
            _focusimg.LayoutParameters = layoutParams;
            _focusimg.Visibility = ViewStates.Visible;
        }
        catch (System.Exception ex)
        {
            Console.WriteLine(ex.ToString());
            Console.Write(ex.StackTrace);
        }
        //return true;
    }
    else
    {
        //return false;
    }
}

And to make it disappear you can code in the success state in AutoFocusCallBack like this:

if (success)
{
    Task.Delay(1000);
    Activity1._focusimg.Visibility = ViewStates.Invisible;
}

In order to make the ImageView accessible from AutoFocusCallBack, you can make it a static one in xamarin:

private Android.Hardware.Camera _camera;
private TextureView _textureView;

public static ImageView _focusimg;

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);
    SetContentView(Resource.Layout.Main);

    _textureView = (TextureView)this.FindViewById(Resource.Id.textureView);
    _textureView.SurfaceTextureListener = this;
    _textureView.Touch += _textureView_Touch;

    var tpBtn = (Button)this.FindViewById(Resource.Id.take_photo);
    tpBtn.Click += TpBtn_Click;

    _focusimg = (ImageView)this.FindViewById(Resource.Id.focuscircle);
}

This works fine every time when it successfully set focus, but I found that sometimes when I tapped on TextureView, it will not fire the _textureView_Touch event, I didn't deep dig this problem.

Another method I thought was to draw a circle dynamically on Canvas like you did in your code, it also worked, but I found my demo responses slow using this method, and I also didn't deep dig this issue. Anyway, I think the first method using a Image is simpler, if a demo is needed, please leave a comment.

Upvotes: 1

Related Questions