Skip to content

Commit 97fe90d

Browse files
Vetle444CopilotCopilot
authored
Camera Zoom Fixes (#850)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent a3ee42a commit 97fe90d

6 files changed

Lines changed: 128 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## [57.1.1]
2+
- [iOS][Camera] Fixed focus locking on startup by enabling continuous autofocus and auto-exposure when camera session starts
3+
- [iOS][Camera] Fixed tap-to-focus permanently locking focus by returning to continuous autofocus after one-shot focus completes
4+
- [iOS][Camera] Fixed zoom not triggering refocus by re-engaging continuous autofocus after pinch-to-zoom and slider zoom changes
5+
- [Android][Camera] Fixed zoom not triggering refocus by starting a new focus metering action after pinch-to-zoom
6+
- [Android][Camera] Fixed tap-to-focus indicator appearing at wrong position by using view-relative touch coordinates and correcting for preview translation offset
7+
18
## [57.1.0]
29
- [LayoutDiagnostics] Added runtime layout diagnostics API for profiling measure/arrange counts per element type. Includes a floating overlay visible over modals and bottom sheets, automatic snapshot capture during page and bottom sheet lifecycle, per-instance thrashing detection, and JSON export.
310

src/library/DIPS.Mobile.UI/API/Camera/Preview/Android/PreviewView/PreviewViewHandler.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,32 @@ protected override void ConnectHandler(AndroidX.Camera.View.PreviewView platform
2121
scaleFactor =>
2222
{
2323
OnScaled?.Invoke(scaleFactor);
24+
}, () =>
25+
{
26+
OnScaleEnded?.Invoke();
2427
}, (x, y) =>
2528
{
2629
OnTapped?.Invoke(x, y);
2730
}));
2831
}
2932

3033
public event Action<float>? OnScaled;
34+
public event Action? OnScaleEnded;
3135
public event Action<float, float>? OnTapped;
3236
}
3337

3438
internal class TouchScaleListener : Object, View.IOnTouchListener, ScaleGestureDetector.IOnScaleGestureListener
3539
{
3640
private readonly Action<float>? m_onScale;
41+
private readonly Action? m_onScaleEnd;
3742
private readonly Action<float, float>? m_onTouch;
3843
private readonly ScaleGestureDetector m_scaleGestureDetector;
3944
private bool m_ignoreTouchUp;
4045

41-
public TouchScaleListener(Action<float>? onScale, Action<float, float>? onTouch)
46+
public TouchScaleListener(Action<float>? onScale, Action? onScaleEnd, Action<float, float>? onTouch)
4247
{
4348
m_onScale = onScale;
49+
m_onScaleEnd = onScaleEnd;
4450
m_onTouch = onTouch;
4551

4652
m_scaleGestureDetector = new ScaleGestureDetector(Application.Context, this);
@@ -56,7 +62,7 @@ public bool OnTouch(View? v, MotionEvent? e)
5662
if (e.Action == MotionEventActions.Up)
5763
{
5864
if(!m_ignoreTouchUp)
59-
m_onTouch?.Invoke(e.RawX, e.RawY);
65+
m_onTouch?.Invoke(e.GetX(), e.GetY());
6066

6167
m_ignoreTouchUp = false;
6268
}
@@ -76,5 +82,8 @@ public bool OnScaleBegin(ScaleGestureDetector detector)
7682
return true;
7783
}
7884

79-
public void OnScaleEnd(ScaleGestureDetector detector) {}
85+
public void OnScaleEnd(ScaleGestureDetector detector)
86+
{
87+
m_onScaleEnd?.Invoke();
88+
}
8089
}

src/library/DIPS.Mobile.UI/API/Camera/Preview/CameraPreview.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,7 @@ internal void AddFocusIndicator(float percentX, float percentY)
167167
m_indicatorWrapper.TranslationX = percentX * PreviewView.Width - m_indicator.WidthRequest / 2;
168168
m_indicatorWrapper.TranslationY = percentY * PreviewView.Height;
169169

170-
#if __IOS__
171170
m_indicatorWrapper.TranslationY -= (m_indicator.HeightRequest / 2) - PreviewView.TranslationY;
172-
#else
173-
m_indicatorWrapper.TranslationY -= m_indicator.HeightRequest / 1.25f;
174-
#endif
175171

176172
m_indicator.ScaleTo(1, easing: Easing.SpringOut);
177173
m_indicator.FadeTo(1);

src/library/DIPS.Mobile.UI/API/Camera/Preview/iOS/PreviewView.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,25 @@ private void PinchToZoom(UIPinchGestureRecognizer pinchRecognizer, AVCaptureDevi
120120
}
121121
}
122122
}
123+
else if (pinchRecognizer.State is UIGestureRecognizerState.Ended or UIGestureRecognizerState.Cancelled)
124+
{
125+
// Re-trigger continuous autofocus after zoom change so the camera
126+
// adjusts focus to the new effective distance.
127+
if (captureDevice.LockForConfiguration(out _))
128+
{
129+
try
130+
{
131+
if (captureDevice.IsFocusModeSupported(AVCaptureFocusMode.ContinuousAutoFocus))
132+
{
133+
captureDevice.FocusMode = AVCaptureFocusMode.ContinuousAutoFocus;
134+
}
135+
}
136+
finally
137+
{
138+
captureDevice.UnlockForConfiguration();
139+
}
140+
}
141+
}
123142
}
124143

125144
internal void RemovePinchToZoom()
@@ -286,6 +305,10 @@ internal void SetFocusPoint(double x, double y, AVCaptureDevice captureDevice, o
286305
{
287306
captureDevice.UnlockForConfiguration();
288307
}
308+
309+
// Return to continuous autofocus after one-shot focus completes,
310+
// so the camera can re-adjust when distance changes.
311+
ObserveFocusAndRestoreContinuous(captureDevice);
289312
}
290313
else
291314
{
@@ -296,11 +319,53 @@ internal void SetFocusPoint(double x, double y, AVCaptureDevice captureDevice, o
296319
}
297320
}
298321
}
322+
323+
private IDisposable? m_focusObserver;
324+
325+
private void ObserveFocusAndRestoreContinuous(AVCaptureDevice captureDevice)
326+
{
327+
m_focusObserver?.Dispose();
328+
m_focusObserver = captureDevice.AddObserver(
329+
"adjustingFocus",
330+
NSKeyValueObservingOptions.New,
331+
change =>
332+
{
333+
if (change?.NewValue is NSNumber adjusting && !adjusting.BoolValue)
334+
{
335+
// Focus has settled — restore continuous AF
336+
if (captureDevice.LockForConfiguration(out _))
337+
{
338+
try
339+
{
340+
if (captureDevice.IsFocusModeSupported(AVCaptureFocusMode.ContinuousAutoFocus))
341+
{
342+
captureDevice.FocusMode = AVCaptureFocusMode.ContinuousAutoFocus;
343+
}
344+
345+
if (captureDevice.IsExposureModeSupported(AVCaptureExposureMode.ContinuousAutoExposure))
346+
{
347+
captureDevice.ExposureMode = AVCaptureExposureMode.ContinuousAutoExposure;
348+
}
349+
}
350+
finally
351+
{
352+
captureDevice.UnlockForConfiguration();
353+
}
354+
}
355+
356+
m_focusObserver?.Dispose();
357+
m_focusObserver = null;
358+
}
359+
});
360+
}
299361

300362
public new void Dispose()
301363
{
302364
m_avCaptureDevice = null;
303365

366+
m_focusObserver?.Dispose();
367+
m_focusObserver = null;
368+
304369
RemovePinchToZoom();
305370
RemoveTouchToFocus();
306371
RemovePreviewLayer();

src/library/DIPS.Mobile.UI/API/Camera/Shared/Android/CameraFragment.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ private void AddPinchToZoom()
290290
if(PreviewViewHandler is not null)
291291
{
292292
PreviewViewHandler.OnScaled += OnScaled;
293+
PreviewViewHandler.OnScaleEnded += TriggerContinuousAutoFocus;
293294
}
294295
}
295296

@@ -342,6 +343,21 @@ private void OnChangedZoomRatio(float zoomRatio)
342343

343344
CameraControl?.SetZoomRatio(zoomRatio);
344345
}
346+
347+
private void TriggerContinuousAutoFocus()
348+
{
349+
if (PreviewView is null || CameraControl is null)
350+
return;
351+
352+
// Create a center-point focus action to briefly re-engage autofocus after zoom
353+
// changes, then automatically return control to the camera's normal behavior.
354+
var centerPoint = PreviewView.MeteringPointFactory.CreatePoint(
355+
PreviewView.Width / 2f, PreviewView.Height / 2f);
356+
var action = new FocusMeteringAction.Builder(centerPoint, FocusMeteringAction.FlagAf)
357+
.SetAutoCancelDuration(2, TimeUnit.Seconds)
358+
.Build();
359+
CameraControl.StartFocusAndMetering(action);
360+
}
345361

346362
public override void OnConfigurationChanged(Configuration newConfig)
347363
{
@@ -409,6 +425,7 @@ private void UnRegisterRotationEvents()
409425
if (PreviewViewHandler is not null)
410426
{
411427
PreviewViewHandler.OnScaled -= OnScaled;
428+
PreviewViewHandler.OnScaleEnded -= TriggerContinuousAutoFocus;
412429
PreviewViewHandler.OnTapped -= PreviewViewOnTapped;
413430
}
414431

src/library/DIPS.Mobile.UI/API/Camera/Shared/iOS/CameraSession.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ internal async Task ConfigureAndStart(CameraPreview cameraPreview, int? maxHeigh
180180
m_avCaptureOutput); //this has to be set before setting metadata objects, or else it crashes
181181

182182
ConfigureSession();
183+
184+
SetContinuousAutoFocus();
183185

184186
//Commit the configuration
185187
CaptureSession.CommitConfiguration();
@@ -319,12 +321,37 @@ private void OnChangedZoomRatio(float zoomRatio)
319321
{
320322
CaptureDevice.UnlockForConfiguration();
321323
}
324+
325+
SetContinuousAutoFocus();
322326
}
323327

324328
public abstract void ConfigureSession();
325329

326330
public abstract AVCaptureDevice? SelectCaptureDevice();
327331

332+
private void SetContinuousAutoFocus()
333+
{
334+
if (CaptureDevice is null || !CaptureDevice.LockForConfiguration(out _))
335+
return;
336+
337+
try
338+
{
339+
if (CaptureDevice.IsFocusModeSupported(AVCaptureFocusMode.ContinuousAutoFocus))
340+
{
341+
CaptureDevice.FocusMode = AVCaptureFocusMode.ContinuousAutoFocus;
342+
}
343+
344+
if (CaptureDevice.IsExposureModeSupported(AVCaptureExposureMode.ContinuousAutoExposure))
345+
{
346+
CaptureDevice.ExposureMode = AVCaptureExposureMode.ContinuousAutoExposure;
347+
}
348+
}
349+
finally
350+
{
351+
CaptureDevice.UnlockForConfiguration();
352+
}
353+
}
354+
328355
internal void OnCameraFailed<T>(CameraException exception, bool shouldOnlyLog = false) where T : class
329356
{
330357
DUILogService.LogError<T>(exception.Message);

0 commit comments

Comments
 (0)