0

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?

Community
  • 1
  • 1
Euridice01
  • 2,510
  • 11
  • 44
  • 76

3 Answers3

3

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.

albilaga
  • 419
  • 1
  • 10
  • 26
  • For my second question, can you give me an example. I tried textureView.Click += OnTouch; but 'no overload for 'OnTouch' matches delegate 'EventHandler'. Is there a specific event handler I need to use? – Euridice01 Dec 01 '16 at 01:13
  • Thanks blueboyz! That seems to work. The casting is not valid in the CalculateTapArea() @_@ I guess I have to debug that but that means the tap event was registered – Euridice01 Dec 01 '16 at 01:50
  • Glad it works. Btw what kind of error In CalculateTapArea()? In that method I don't think you need to convert float value by using Java.Float – albilaga Dec 01 '16 at 02:05
  • Fixed the type converter! I'm getting another issue with setparameters but I think it's because the phone I'm using doesn't have continue picture, so I need to set it to another supported mode. Going to try that. Thank you! – Euridice01 Dec 01 '16 at 02:09
  • I got the focus to work. I am wondeirng though what are GenericParameterAttributes and GenericParameterPosition? I get exceptions for those parameter properties (Although they are catch in the exception and focus still works). Are you familiar with what those properties are? Thanks! – Euridice01 Dec 01 '16 at 02:36
  • Nope. Try read some msdn docs in here https://msdn.microsoft.com/en-us/library/system.reflection.genericparameterattributes(v=vs.110).aspx Hope it helps – albilaga Dec 01 '16 at 03:53
  • camera.AutoFocus(this); Crashes my app instead use parameter.FocusMode = from here: https://stackoverflow.com/questions/15623944/how-to-autofocus-android-camera-automatically – bh_earth0 Feb 22 '18 at 11:52
1

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. :-)

jgoldberger - MSFT
  • 5,978
  • 2
  • 20
  • 44
  • Good point! I also ended up creating an inner class that implements the OnAutoFocus method from the IAutoFocusCallback interface and creating the instance of the class inside camera.AutoFocus()... Just need to test it. That would also work but "this" would be a shorter way of doing the same thing right? Which way is clearer/better practice? – Euridice01 Dec 01 '16 at 01:05
0

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

}
Michal Diviš
  • 2,008
  • 12
  • 19