Euridice01
Euridice01

Reputation: 2568

Tap to focus for camera implementation

I'm trying to implement a manual focus feature for my camera page so that the user can tap to focus the camera.

I'm following this StackOverflow question that's currently written in Java for native Android. I've been converting it to C# for my Xamarin.Forms Android app.

Here's what I have so far:

public class CameraPage : PageRenderer, TextureView.ISurfaceTextureListener, Android.Views.View.IOnTouchListener, IAutoFocusCallback
{
    global::Android.Hardware.Camera camera;
    TextureView textureView;

    public void OnAutoFocus(bool success, Android.Hardware.Camera camera)
    {
        var parameters = camera.GetParameters();
        if (parameters.FocusMode != Android.Hardware.Camera.Parameters.FocusModeContinuousPicture)
        {
            parameters.FocusMode = Android.Hardware.Camera.Parameters.FocusModeContinuousPicture;

            if (parameters.MaxNumFocusAreas > 0)
            {
                parameters.FocusAreas = null;
            }
            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(OnAutoFocus); //Here is the issue. How do I use the callback? 
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex.ToString());
                Console.Write(ex.StackTrace);
            }
            return true;
        }
        return false; 
    }

    private Rect CalculateTapArea(object x, object y, float coefficient)
    {
        var focusAreaSize = 500;
        int areaSize = Java.Lang.Float.ValueOf(focusAreaSize * coefficient).IntValue();

        int left = clamp((int)x - areaSize / 2, 0, textureView.Width - areaSize);
        int top = clamp((int)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;
    }
}

I've managed to convert most of it but I'm not sure how to properly use the AutoFocusCallback here. What should I do to call OnAutoFocus from my OnTouch event like in the java answer I linked above?

After I attached the callback, then all I need to do is subscribe the OnTouch event to my page correct or...?

For example, I tried:

textureView.Click += OnTouch; but 'no overload for 'OnTouch' matches delegate 'EventHandler'. Is there a specific event handler I need to use?

Upvotes: 0

Views: 3029

Answers (3)

Michal Diviš
Michal Diviš

Reputation: 2206

Since there's no complete example here, here's mine.

This solution works fine, at least for me. The camera will focus continously until a focus point is tapped. It will then focus on the tap point until you move the camera away. Then it goes back to continous focus mode.

public class CameraPageRenderer : PageRenderer, TextureView.ISurfaceTextureListener, Android.Hardware.Camera.IPictureCallback, Android.Hardware.Camera.IShutterCallback, IAutoFocusCallback 
{

    // ... code removed for brevity

    /// <summary>
    /// Occurs whenever the user touches the screen. Here we set the focus mode to FocusModeAuto and set a focus area based on the tapped coordinates.
    /// </summary>
    public override bool OnTouchEvent(MotionEvent e)
    {
        var parameters = camera.GetParameters();

        parameters.FocusMode = Camera.Parameters.FocusModeAuto;

        if (parameters.MaxNumFocusAreas > 0)
        {
            var focusRect = CalculateTapArea(e.GetX(), e.GetY(), textureView.Width, textureView.Height, 50f);

            parameters.FocusAreas = new List<Area>()
            {
                new Area(focusRect, 1000)
            };
        }

        try
        {
            camera.CancelAutoFocus();
            camera.SetParameters(parameters);
            camera.AutoFocus(this);
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex);
        }

        return true;
    }

    /// <summary>
    /// Auto focus callback. Here we reset the focus mode to FocusModeContinuousPicture and remove any focus areas
    /// </summary>
    public void OnAutoFocus(bool success, Camera camera)
    {
        var parameters = camera.GetParameters();

        parameters.FocusMode = Parameters.FocusModeContinuousPicture;

        if (parameters.MaxNumFocusAreas > 0)
        {
            parameters.FocusAreas = null;
        }

        camera.SetParameters(parameters);
    }

    /// <summary>
    /// Calculates a tap area using the focus coordinates mentioned in <see href="https://developer.android.com/reference/android/hardware/Camera.Parameters.html#getFocusAreas()"/>
    /// <para>
    /// Coordinates of the rectangle range from -1000 to 1000. (-1000, -1000) is the upper left point. (1000, 1000) is the lower right point. The width and height of focus areas cannot be 0 or negative.</para>
    /// </summary>
    /// <param name="x">The X coordinate of the tapped area</param>
    /// <param name="y">The Y coordinate of the tapped area</param>
    /// <param name="width">The total width of the tappable area</param>
    /// <param name="height">The total height of the tappable area</param>
    /// <param name="focusAreaSize">The desired size (widht, height) of the created rectangle</param>
    /// <returns></returns>
    private Rect CalculateTapArea(float x, float y, float width, float height, float focusAreaSize)
    {
        var leftFloat = x * 2000 / width - 1000;
        var topFloat = y * 2000 / height - 1000;

        var left = RoundFocusCoordinate(leftFloat);
        var top = RoundFocusCoordinate(topFloat);
        var right = RoundFocusCoordinate(leftFloat + focusAreaSize);
        var bottom = RoundFocusCoordinate(topFloat + focusAreaSize);

        return new Rect(left, top, right, bottom);
    }

    /// <summary>
    /// Round, convert to int, and clamp between -1000 and 1000
    /// </summary>
    private int RoundFocusCoordinate(float value)
    {
        var intValue = (int)Math.Round(value, 0, MidpointRounding.AwayFromZero);
        return Math.Clamp(intValue, -1000, 1000);
    }

    // ... code removed for brevity

}

Upvotes: 0

albilaga
albilaga

Reputation: 439

You can try change

camera.AutoFocus(OnAutoFocus);

to

camera.AutoFocus(this);

and it will be using OnAutoFocus because it implementation from IAutoFocusCallback.

And for your question about subscribe event you can try to subscribe event in OnElementChanged like this

protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
        {
            base.OnElementChanged(e);
            if (e.OldElement != null || Element == null)
            {
                return;
            }
            try
            {
                this.SetOnTouchListener(this);
            }
            catch (Exception e)
            {

            }

        }

And btw I don't see to use TextureView.ISurfaceTextureListener in this code.

Upvotes: 3

jgoldberger - MSFT
jgoldberger - MSFT

Reputation: 6088

All that happened in the linked Java answer is that they provided the code to run when the OS calls the callback:

camera.autoFocus(new Camera.AutoFocusCallback() {                   
    @Override
      public void onAutoFocus(boolean success, Camera camera) {
           camera.cancelAutoFocus();
           Parameters params = camera.getParameters();
           if(params.getFocusMode() != Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE){
                params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
                camera.setParameters(params);
        }
    }
});

the above does not "call" the call back, just provides the call back code to run. the OS calls the call back. So in Xamarin, you need to pass in the type that is implementing the IAutoFocusCallback interface, so You should be able to do this I would think since CameraPage is implementing the IAutoFocusCallback interface:

camera.AutoFocus(this); // "this" refers to your current CameraPage which implements the interface. 

the clue here is that when you type the opening parenthesis after camera.AutoFocus the popup shows that you need to pass in a type IAutoFocusCallback, which means any type that implements that interface, so in this case that is "this" CameraPage. :-)

Upvotes: 1

Related Questions