Here is the iOS implementation:
#nullable enable using System.Diagnostics; using System.Runtime.InteropServices; using AVFoundation; using CoreGraphics; using CoreVideo; using Foundation; using Microsoft.Maui.Controls; using UIKit; using VideoDemos.Controls; using WoundMatrixSmall.DependencyServices.MultiTargeting; using WoundMatrixSmall.Utilities; using WoundMatrixSmall.ViewModels; using WoundMatrixSmall.Views; namespace VideoDemos.Platforms.MaciOS { public class CameraPersonViewer : UIView, IAVCapturePhotoCaptureDelegate { private AVCaptureSession captureSession; private AVCaptureDeviceInput captureDeviceInput; private AVCapturePhotoOutput capturePhotoOutput; private UITextView textViewDelayCountdown; private UIView liveCameraStream; private UIButton takePhotoButton, cancelCameraButton, takeDelayedPhotoButton, toggleCameraButton, toggleFlashButton; private UIImageView autoFocusImage; private UIImageView orientationImage; private CameraPerson currentCameraPerson; int timeSpan = 0; private bool flashOn = false; double rectangleHeight = 0; double rectangleWidth = 0; double rectangleMargin = 0; public CameraPersonViewer(CameraPerson cameraPerson) { currentCameraPerson = cameraPerson; try { SetupUserInterface(); SetupEventHandlers(); SetupLiveCameraStream(); AuthorizeCameraUse(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(@" ERROR: ", ex.Message); } } public void SetupUserInterface() { try { NFloat centerButtonX = UIScreen.MainScreen.Bounds.GetMidX(); NFloat centerButtonY = UIScreen.MainScreen.Bounds.GetMidY(); NFloat topLeftX = UIScreen.MainScreen.Bounds.X + 10; NFloat topRightX = UIScreen.MainScreen.Bounds.Right - 10; NFloat bottomButtonY = UIScreen.MainScreen.Bounds.Bottom - 10; NFloat topButtonY = UIScreen.MainScreen.Bounds.Top + 10; int buttonWidth = 0; int buttonHeight = 0; NFloat centerX = UIScreen.MainScreen.Bounds.GetMidX(); NFloat centerY = UIScreen.MainScreen.Bounds.GetMidY(); if (Device.Idiom == TargetIdiom.Tablet) { rectangleHeight = 325; rectangleWidth = 325; rectangleMargin = 162; buttonWidth = 70; buttonHeight = 70; } else if (Device.Idiom == TargetIdiom.Phone) { rectangleHeight = 225; rectangleWidth = 225; rectangleMargin = 112; buttonWidth = 60; buttonHeight = 60; } liveCameraStream = new UIView() { Frame = new CGRect(0f, 0f, UIScreen.MainScreen.Bounds.Width, UIScreen.MainScreen.Bounds.Height) }; if (liveCameraStream != null) { // Rotate image to the correct display orientation liveCameraStream.Transform = CGAffineTransform.MakeRotation((float)Math.PI * 1.5F); liveCameraStream.Frame = new CGRect(0f, 0f, UIScreen.MainScreen.Bounds.Width, UIScreen.MainScreen.Bounds.Height); } textViewDelayCountdown = new UITextView() { Frame = new CGRect(centerButtonX - 30, centerButtonY - 75, 110, 110) }; textViewDelayCountdown.TextColor = UIColor.FromRGB(96, 166, 67); textViewDelayCountdown.BackgroundColor = UIColor.FromRGBA(255, 255, 255, 0); textViewDelayCountdown.Font = UIFont.SystemFontOfSize(100f); UIImage uiImage = new UIImage("icon_focus_off.png"); autoFocusImage = new UIImageView(uiImage) { Frame = new CGRect(centerX - rectangleMargin, centerY - rectangleMargin, rectangleHeight, rectangleWidth) }; if (currentCameraPerson.ImageType != CameraPerson.IMAGE_TYPE.Profile) { UIImage orientImage = new UIImage("orientationimage.png"); orientationImage = new UIImageView(orientImage) { Frame = new CGRect(5, (centerY - 185), 200, 375) }; } cancelCameraButton = new UIButton() { Frame = new CGRect(topRightX - 100, centerButtonY + 120, buttonWidth, buttonHeight) }; cancelCameraButton.SetBackgroundImage(UIImage.FromFile("icon_camera_close.png"), UIControlState.Normal); toggleFlashButton = new UIButton() { Frame = new CGRect(topRightX - 100, centerButtonY + 45, buttonWidth, buttonHeight) }; toggleFlashButton.SetBackgroundImage(UIImage.FromFile("icon_camera_flash_off.png"), UIControlState.Normal); takePhotoButton = new UIButton() { Frame = new CGRect(topRightX - 100, centerButtonY - 30, buttonWidth, buttonHeight) }; takePhotoButton.SetBackgroundImage(UIImage.FromFile("icon_camera_take.png"), UIControlState.Normal); takeDelayedPhotoButton = new UIButton() { Frame = new CGRect(topRightX - 100, centerButtonY - 105, buttonWidth, buttonHeight) }; takeDelayedPhotoButton.SetBackgroundImage(UIImage.FromFile("icon_camera_take_timer.png"), UIControlState.Normal); toggleCameraButton = new UIButton() { Frame = new CGRect(topRightX - 100, centerButtonY - 180, buttonWidth, buttonHeight) }; toggleCameraButton.SetBackgroundImage(UIImage.FromFile("icon_camera_switch.png"), UIControlState.Normal); UIApplication.SharedApplication.StatusBarHidden = true; this.Add(liveCameraStream); this.Add(textViewDelayCountdown); this.Add(takePhotoButton); this.Add(takeDelayedPhotoButton); this.Add(toggleCameraButton); this.Add(toggleFlashButton); this.Add(cancelCameraButton); this.Add(autoFocusImage); if (currentCameraPerson.ImageType == CameraPerson.IMAGE_TYPE.Wound) { this.Add(orientationImage); } } catch (Exception ex) { Debug.WriteLine(ex); } } public void SetupEventHandlers() { takePhotoButton.TouchUpInside += (object? sender, EventArgs e) => { CapturePhoto(); }; toggleCameraButton.TouchUpInside += (object? sender, EventArgs e) => { ToggleFrontBackCamera(); }; cancelCameraButton.TouchUpInside += async (object? sender, EventArgs e) => { await CancelCamera(false); }; toggleFlashButton.TouchUpInside += (object? sender, EventArgs e) => { ToggleFlash(); }; takeDelayedPhotoButton.TouchUpInside += (object? sender, EventArgs e) => { DelayCaptureTimerStart(); }; } public async Task CancelCamera(bool isAccepted) { try { captureSession.StopRunning(); UIApplication.SharedApplication.StatusBarHidden = false; if (!isAccepted) { Device.BeginInvokeOnMainThread(async () => { //INavigation mainNav = Application.Current.MainPage.Navigation; //mainNav.RemovePage(mainNav.NavigationStack[mainNav.NavigationStack.Count - 2]); await Application.Current.MainPage.Navigation.PopAsync(); }); OrientationService.ForcePortrait(); // Give the phone a chance to rotate the screen before opening the view up. await Task.Delay(250); } } catch (Exception ex) { //throw ex; } } [Export("captureOutput:didFinishProcessingPhoto:error:")] public virtual async void DidFinishProcessingPhoto(AVCapturePhotoOutput captureOutput, AVCapturePhoto photo, NSError error) { if (error != null) { Console.WriteLine($"Error capturing photo: {error}", error); return; } NSData? jpegImage = photo.FileDataRepresentation; UIImage? photoX = new UIImage(jpegImage); byte[] myByteArray = new byte[jpegImage.Length]; System.Runtime.InteropServices.Marshal.Copy(jpegImage.Bytes, myByteArray, 0, Convert.ToInt32(jpegImage.Length)); string captureDate = CommonUtils.ConvertDateToLong(DateTime.Now).ToString(); if (currentCameraPerson.CameraType == CameraPerson.CAMERA_TYPE.Forward) { myByteArray = RotateImage(photoX, 180); } await CancelCamera(true); if (currentCameraPerson.ImageType == CameraPerson.IMAGE_TYPE.Wound) { await ShowConfirmWoundImage(myByteArray, captureDate); } else { await ShowProfileImage(myByteArray, captureDate); } } public async void CapturePhoto() { try { SetFlashOnStart(); // Camera facing Type AVCaptureDevicePosition devicePosition = captureDeviceInput.Device.Position; AVCaptureDevice device = GetCameraForOrientation(devicePosition); ConfigureCameraForDevice(device); await Task.Delay(500); AVCapturePhotoSettings settings = AVCapturePhotoSettings.Create(); AVCaptureConnection? videoConnection = capturePhotoOutput.ConnectionFromMediaType(new NSString(AVMediaTypes.Video.ToString())); capturePhotoOutput.CapturePhoto(settings, this); // Above line calls DidFinishProcessingPhoto Called } catch (Exception ex) { Console.Write(ex.Message); } } private async Task ShowConfirmWoundImage(byte[] myByteArray, string captureDate) { await Task.Run(() => { ConfirmWoundImageViewModel vm = new ConfirmWoundImageViewModel(myByteArray, currentCameraPerson.PatientId, currentCameraPerson.CameraType, currentCameraPerson.LocalId, captureDate); ConfirmWoundImagePage vw = new ConfirmWoundImagePage { BindingContext = vm }; Device.BeginInvokeOnMainThread(async () => { await Application.Current.MainPage.Navigation.PushAsync(vw); }); }); } private async Task ShowProfileImage(byte[] myByteArray, string captureDate) { await Task.Run(() => { ConfirmPatientImageViewModel vm = new ConfirmPatientImageViewModel(myByteArray, currentCameraPerson.PatientId); ConfirmPatientImagePage vw = new ConfirmPatientImagePage { BindingContext = vm }; Device.BeginInvokeOnMainThread(async () => { await Application.Current.MainPage.Navigation.PushAsync(vw); }); }); } public void ToggleFrontBackCamera() { AVCaptureDevicePosition devicePosition = captureDeviceInput.Device.Position; if (devicePosition == AVCaptureDevicePosition.Front) { currentCameraPerson.CameraType = CameraPerson.CAMERA_TYPE.Back; devicePosition = AVCaptureDevicePosition.Back; } else { currentCameraPerson.CameraType = CameraPerson.CAMERA_TYPE.Forward; devicePosition = AVCaptureDevicePosition.Front; } AVCaptureDevice device = GetCameraForOrientation(devicePosition); //ConfigureCameraForDevice(device); captureSession.BeginConfiguration(); captureSession.RemoveInput(captureDeviceInput); captureDeviceInput = AVCaptureDeviceInput.FromDevice(device); captureSession.AddInput(captureDeviceInput); captureSession.CommitConfiguration(); } public void SetFlashOnStart() { AVCaptureDevice device = captureDeviceInput.Device; NSError error = new NSError(); if (device.HasFlash) { if (flashOn) { device.LockForConfiguration(out error); device.FlashMode = AVCaptureFlashMode.On; device.UnlockForConfiguration(); } else { device.LockForConfiguration(out error); device.FlashMode = AVCaptureFlashMode.Off; device.UnlockForConfiguration(); } } } public void ToggleFlash() { try { AVCaptureDevice device = captureDeviceInput.Device; NSError error = new NSError(); if (device.HasFlash) { if (flashOn) { flashOn = false; toggleFlashButton.SetBackgroundImage(UIImage.FromFile("icon_camera_flash_off.png"), UIControlState.Normal); } else { flashOn = true; toggleFlashButton.SetBackgroundImage(UIImage.FromFile("icon_camera_flash_on.png"), UIControlState.Normal); } } else { flashOn = false; toggleFlashButton.SetBackgroundImage(UIImage.FromFile("icon_camera_flash_off.png"), UIControlState.Normal); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("@Error: " + ex.Message); } } public byte[] RotateImage(UIImage image, float degree) { float Radians = degree * (float)Math.PI / 180; UIView view = new UIView(frame: new CGRect(0, 0, image.Size.Width, image.Size.Height)); CGAffineTransform t = CGAffineTransform.MakeRotation(Radians); view.Transform = t; CGSize size = view.Frame.Size; UIGraphics.BeginImageContext(size); CGContext context = UIGraphics.GetCurrentContext(); context.TranslateCTM(size.Width / 2, size.Height / 2); context.RotateCTM(Radians); context.ScaleCTM(1, -1); context.DrawImage(new CGRect(-image.Size.Width / 2, -image.Size.Height / 2, image.Size.Width, image.Size.Height), image.CGImage); UIImage imageCopy = UIGraphics.GetImageFromCurrentImageContext(); UIGraphics.EndImageContext(); byte[] rotatedBytes; using (NSData imageData = imageCopy.AsPNG()) { byte[] myByteArray = new Byte[imageData.Length]; System.Runtime.InteropServices.Marshal.Copy(imageData.Bytes, myByteArray, 0, Convert.ToInt32(imageData.Length)); rotatedBytes = myByteArray; } return rotatedBytes; } public void DelayCaptureTimerStart() { timeSpan = 5; NSTimer timer = NSTimer.CreateRepeatingTimer(1, t => { if (!OnTimerTick()) t.Invalidate(); }); NSRunLoop.Main.AddTimer(timer, NSRunLoopMode.Common); } bool OnTimerTick() { // Set the Text property of the Label. while (timeSpan > 0) { textViewDelayCountdown.Text = timeSpan.ToString(); timeSpan--; return true; } textViewDelayCountdown.Text = ""; CapturePhoto(); return false; } public void SetupLiveCameraStream() { try { captureSession = new AVCaptureSession(); CoreAnimation.CALayer viewLayer = liveCameraStream.Layer; AVCaptureVideoPreviewLayer videoPreviewLayer = new AVCaptureVideoPreviewLayer(captureSession) { Frame = liveCameraStream.Bounds }; videoPreviewLayer.VideoGravity = AVLayerVideoGravity.ResizeAspectFill; liveCameraStream.Layer.AddSublayer(videoPreviewLayer); #pragma warning disable CS0618 // Type or member is obsolete AVCaptureDevice captureDevice = AVCaptureDevice.GetDefaultDevice(AVMediaTypes.Video); #pragma warning restore CS0618 // Type or member is obsolete //ConfigureCameraForDevice(captureDevice); captureDeviceInput = AVCaptureDeviceInput.FromDevice(captureDevice); NSMutableDictionary dictionary = new NSMutableDictionary(); dictionary[AVVideo.CodecKey] = new NSNumber((int)AVVideoCodec.JPEG); capturePhotoOutput = new AVCapturePhotoOutput(); captureSession.AddOutput(capturePhotoOutput); captureSession.AddInput(captureDeviceInput); captureSession.StartRunning(); // Camera facing Type AVCaptureDevicePosition devicePosition = captureDeviceInput.Device.Position; if (currentCameraPerson.CameraType == CameraPerson.CAMERA_TYPE.Back) { devicePosition = AVCaptureDevicePosition.Back; } else { devicePosition = AVCaptureDevicePosition.Front; } AVCaptureDevice device = GetCameraForOrientation(devicePosition); //ConfigureCameraForDevice(device); captureSession.BeginConfiguration(); captureSession.RemoveInput(captureDeviceInput); captureDeviceInput = AVCaptureDeviceInput.FromDevice(device); captureSession.AddInput(captureDeviceInput); captureSession.CommitConfiguration(); //End } catch (Exception ex) { Console.WriteLine(ex.Message); } } public AVCaptureDevice? GetCameraForOrientation(AVCaptureDevicePosition orientation) { if (currentCameraPerson.CameraType == CameraPerson.CAMERA_TYPE.Forward) { AVCaptureDeviceDiscoverySession discoverySession = AVCaptureDeviceDiscoverySession.Create(new AVCaptureDeviceType[] { AVCaptureDeviceType.BuiltInWideAngleCamera }, AVMediaTypes.Video, AVCaptureDevicePosition.Front); foreach (AVCaptureDevice device in discoverySession.Devices) { if (device.Position == orientation) { return device; } } } else { return AVCaptureDevice.GetDefaultDevice(AVMediaTypes.Video); } return null; } public void ConfigureCameraForDevice(AVCaptureDevice device) { NFloat centerX = UIScreen.MainScreen.Bounds.GetMidX(); NFloat centerY = UIScreen.MainScreen.Bounds.GetMidY(); NSError error = new NSError(); if (device.IsFocusModeSupported(AVCaptureFocusMode.ContinuousAutoFocus)) { device.LockForConfiguration(out error); device.FocusMode = AVCaptureFocusMode.ContinuousAutoFocus; device.UnlockForConfiguration(); UIImage uiImage = new UIImage("icon_focus_on"); autoFocusImage = new UIImageView(uiImage) { Frame = new CGRect(centerX - rectangleMargin, centerY - rectangleMargin, rectangleHeight, rectangleWidth) }; this.Add(autoFocusImage); } else if (device.IsExposureModeSupported(AVCaptureExposureMode.ContinuousAutoExposure)) { device.LockForConfiguration(out error); device.ExposureMode = AVCaptureExposureMode.ContinuousAutoExposure; device.UnlockForConfiguration(); UIImage uiImage = new UIImage("icon_focus_on"); autoFocusImage = new UIImageView(uiImage) { Frame = new CGRect(centerX - rectangleMargin, centerY - rectangleMargin, rectangleHeight, rectangleWidth) }; this.Add(autoFocusImage); } else if (device.IsWhiteBalanceModeSupported(AVCaptureWhiteBalanceMode.ContinuousAutoWhiteBalance)) { device.LockForConfiguration(out error); device.WhiteBalanceMode = AVCaptureWhiteBalanceMode.ContinuousAutoWhiteBalance; device.UnlockForConfiguration(); UIImage uiImage = new UIImage("icon_focus_on"); autoFocusImage = new UIImageView(uiImage) { Frame = new CGRect(centerX - rectangleMargin, centerY - rectangleMargin, rectangleHeight, rectangleWidth) }; this.Add(autoFocusImage); } } public async void AuthorizeCameraUse() { AVAuthorizationStatus authorizationStatus = AVCaptureDevice.GetAuthorizationStatus(AVAuthorizationMediaType.Video); if (authorizationStatus != AVAuthorizationStatus.Authorized) { await AVCaptureDevice.RequestAccessForMediaTypeAsync(AVAuthorizationMediaType.Video); } } protected override void Dispose(bool disposing) { if (captureDeviceInput != null && captureSession != null) { captureSession.RemoveInput(captureDeviceInput); } if (captureDeviceInput != null) { captureDeviceInput.Dispose(); } if (captureSession != null) { captureSession.StopRunning(); captureSession.Dispose(); } if (capturePhotoOutput != null) { capturePhotoOutput.Dispose(); } base.Dispose(disposing); } } }