From 8ca5936f16f0fa5d5e8dc75f9b432ac9aec11d03 Mon Sep 17 00:00:00 2001 From: scottm Date: Fri, 22 May 2020 11:39:30 -0400 Subject: [PATCH] select the best resolution available that is closest to the surface views aspect ratio --- .../CameraAccess/CameraController.android.cs | 198 ++++++++++++------ 1 file changed, 139 insertions(+), 59 deletions(-) diff --git a/ZXing.Net.Mobile/Android/CameraAccess/CameraController.android.cs b/ZXing.Net.Mobile/Android/CameraAccess/CameraController.android.cs index 484be6074..1e25f7046 100644 --- a/ZXing.Net.Mobile/Android/CameraAccess/CameraController.android.cs +++ b/ZXing.Net.Mobile/Android/CameraAccess/CameraController.android.cs @@ -8,6 +8,7 @@ using Android.Runtime; using Android.Views; using ApxLabs.FastAndroidCamera; +using Xamarin.Essentials; using Camera = Android.Hardware.Camera; namespace ZXing.Mobile.CameraAccess @@ -20,6 +21,7 @@ public class CameraController readonly CameraEventsListener cameraEventListener; int cameraId; IScannerSessionHost scannerHost; + bool needResetCameraBuffers = false; public CameraController(SurfaceView surfaceView, CameraEventsListener cameraEventListener, IScannerSessionHost scannerHost) { @@ -33,16 +35,27 @@ public CameraController(SurfaceView surfaceView, CameraEventsListener cameraEven public Camera Camera { get; private set; } public int LastCameraDisplayOrientationDegree { get; private set; } + public static int LastPreviewWidth { get; private set; } + public static int LastPreviewHeight { get; private set; } public void RefreshCamera() { if (holder == null) return; - ApplyCameraSettings(); - try { + ApplyCameraSettings(); + Camera.SetPreviewDisplay(holder); + + //if ApplyCameraSettings detected a resolution change + //the camera was re-created and we need to reset the buffer and re-set the preview callback + if (needResetCameraBuffers) + { + SetupCameraBuffers(); + Camera.SetNonMarshalingPreviewCallback(cameraEventListener); + needResetCameraBuffers = false; + } Camera.StartPreview(); } catch (Exception ex) @@ -51,6 +64,21 @@ public void RefreshCamera() } } + void SetupCameraBuffers() + { + var previewParameters = Camera.GetParameters(); + var previewSize = previewParameters.PreviewSize; + var bitsPerPixel = ImageFormat.GetBitsPerPixel(previewParameters.PreviewFormat); + + var bufferSize = (previewSize.Width * previewSize.Height * bitsPerPixel) / 8; + const int NUM_PREVIEW_BUFFERS = 5; + for (uint i = 0; i < NUM_PREVIEW_BUFFERS; ++i) + { + using (var buffer = new FastJavaByteArray(bufferSize)) + Camera.AddCallbackBuffer(buffer); + } + } + public void SetupCamera() { if (Camera != null) @@ -62,29 +90,14 @@ public void SetupCamera() if (Camera == null) return; - perf = PerformanceCounter.Start(); - ApplyCameraSettings(); + perf = PerformanceCounter.Start(); try { + ApplyCameraSettings(); Camera.SetPreviewDisplay(holder); - - - var previewParameters = Camera.GetParameters(); - var previewSize = previewParameters.PreviewSize; - var bitsPerPixel = ImageFormat.GetBitsPerPixel(previewParameters.PreviewFormat); - - - var bufferSize = (previewSize.Width * previewSize.Height * bitsPerPixel) / 8; - const int NUM_PREVIEW_BUFFERS = 5; - for (uint i = 0; i < NUM_PREVIEW_BUFFERS; ++i) - { - using (var buffer = new FastJavaByteArray(bufferSize)) - Camera.AddCallbackBuffer(buffer); - } - + SetupCameraBuffers(); Camera.StartPreview(); - Camera.SetNonMarshalingPreviewCallback(cameraEventListener); } catch (Exception ex) @@ -213,6 +226,41 @@ void OpenCamera() } } + CameraResolution GetResolutionForCameraPreview(IList supportedPreviewSizes) + { + CameraResolution resolution = null; + + if (supportedPreviewSizes != null) + { + var availableResolutions = supportedPreviewSizes.Select(sps => new CameraResolution + { + Width = sps.Width, + Height = sps.Height + }); + + // Try and get a desired resolution from the options selector + resolution = scannerHost.ScanningOptions.GetResolution(availableResolutions.ToList()); + + // If the user did not specify a resolution, let's try and find a suitable one + if (resolution == null) + { + resolution = GetOptimalResolutionForCameraPreview(supportedPreviewSizes); + } + } + + // Google Glass requires this fix to display the camera output correctly + if (Build.Model.Contains("Glass")) + { + resolution = new CameraResolution + { + Width = 640, + Height = 360 + }; + } + + return resolution; + } + void ApplyCameraSettings() { if (Camera == null) @@ -224,6 +272,23 @@ void ApplyCameraSettings() if (Camera == null) return; var parameters = Camera.GetParameters(); + + //Get the resolution first to see if it has changed + //if it has changed the camera needs to be destroyed and re-created + //so that the buffer size can be updated + //because GetOptimalResolutionForCameraPreview uses the SurfaceView size and its dimensions + //will likely change upon rotation especially when using a ZXingScannerFragment + var resolution = GetResolutionForCameraPreview(parameters.SupportedPreviewSizes); + if (LastPreviewWidth != 0 && LastPreviewHeight != 0 && (resolution.Width != LastPreviewWidth || resolution.Height != LastPreviewHeight)) + { + needResetCameraBuffers = true; + ShutdownCamera(); + LastPreviewHeight = 0; + LastPreviewWidth = 0; + ApplyCameraSettings(); + return; + } + parameters.PreviewFormat = ImageFormatType.Nv21; var supportedFocusModes = parameters.SupportedFocusModes; @@ -253,45 +318,9 @@ void ApplyCameraSettings() parameters.SetPreviewFpsRange(selectedFps[0], selectedFps[1]); } - CameraResolution resolution = null; - var supportedPreviewSizes = parameters.SupportedPreviewSizes; - if (supportedPreviewSizes != null) - { - var availableResolutions = supportedPreviewSizes.Select(sps => new CameraResolution - { - Width = sps.Width, - Height = sps.Height - }); - - // Try and get a desired resolution from the options selector - resolution = scannerHost.ScanningOptions.GetResolution(availableResolutions.ToList()); - - // If the user did not specify a resolution, let's try and find a suitable one - if (resolution == null) - { - foreach (var sps in supportedPreviewSizes) - { - if (sps.Width >= 640 && sps.Width <= 1000 && sps.Height >= 360 && sps.Height <= 1000) - { - resolution = new CameraResolution - { - Width = sps.Width, - Height = sps.Height - }; - break; - } - } - } - } - // Google Glass requires this fix to display the camera output correctly if (Build.Model.Contains("Glass")) { - resolution = new CameraResolution - { - Width = 640, - Height = 360 - }; // Glass requires 30fps parameters.SetPreviewFpsRange(30000, 30000); } @@ -299,16 +328,67 @@ void ApplyCameraSettings() // Hopefully a resolution was selected at some point if (resolution != null) { - Android.Util.Log.Debug(MobileBarcodeScanner.TAG, - "Selected Resolution: " + resolution.Width + "x" + resolution.Height); + Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "Selected Resolution: " + resolution.Width + "x" + resolution.Height); parameters.SetPreviewSize(resolution.Width, resolution.Height); + LastPreviewWidth = resolution.Width; + LastPreviewHeight = resolution.Height; } Camera.SetParameters(parameters); - SetCameraDisplayOrientation(); } + /// + /// Determines the resolution to use for the camera preview that maintains the closest available + /// aspect ratio to the surface view the camera is being displayed on. + /// using the surface view is especially important if a fragment is being used which may have a much different ratio than the main display + /// + /// Contains all available combinations that camera can use. Dimensions are assuming the camera is in landscape orientation + /// + CameraResolution GetOptimalResolutionForCameraPreview(IList availableResolutions) + { + CameraResolution res = null; + var sortedOptimalResolultionResults = new SortedDictionary(); + + var surfaceViewRatio = surfaceView.Height / (double)surfaceView.Width; + + //the SupportedPreviewSizes from the camera are already sorted highest resolution to smallest, + //but in case a non-sorted list is passed in, sort them + var sortedResolutions = availableResolutions.OrderByDescending(r => r.Height).ToList(); + for (var i = 0; i < sortedResolutions.Count; i++) + { + double resolutionDifference; + var r = sortedResolutions[i]; + + //The Android.Hardware.Camera.Size class assumes all dimensions are with respect to a landscape orientation + //so if orientation is portrait switch them + var orientationHeight = r.Width; + var orientationWidth = r.Height; + + //and if its landscape leave alone + if (DeviceDisplay.MainDisplayInfo.Orientation == DisplayOrientation.Landscape) + { + orientationHeight = r.Height; + orientationWidth = r.Width; + } + + var cameraDisplayAspectRatio = orientationHeight / orientationWidth; + + resolutionDifference = Math.Abs(surfaceViewRatio - cameraDisplayAspectRatio); + + //since the Android.Hardware.Camera API lives in the UpsideDown world switch the width/height back + if (DeviceDisplay.MainDisplayInfo.Orientation == DisplayOrientation.Portrait) + res = new CameraResolution() { Width = (int)orientationHeight, Height = (int)orientationWidth }; + else res = new CameraResolution() { Width = (int)orientationWidth, Height = (int)orientationHeight }; + + //there may be resolutions available that have the same aspect ratio. + //by using the sorted list we will guarantee that we are getting the best resolution available + if (!sortedOptimalResolultionResults.ContainsKey(resolutionDifference)) + sortedOptimalResolultionResults.Add(resolutionDifference, res); + } + return sortedOptimalResolultionResults.First().Value; + } + void AutoFocus(int x, int y, bool useCoordinates) { if (Camera == null) return;