How to Build Xamarin.Forms Barcode QR Code Scanner
Tags: xamarinforms, xamarin, mobile, qrcode
Published: 2022-07-07 07:52:39

Xamarin.Forms lets .NET developers create cross-platform mobile apps for Android and iOS in C#. This article demonstrates how to scan barcode and QR code from image file and live video stream using [Xamarin.Forms Custom Renderers](https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/) and [Dynamsoft Barcode Reader SDK](https://www.dynamsoft.com/barcode-reader/overview/).


## Getting Started with Xamarin.Forms Custom Renderers
Our goal is to create camera preview interface and invoke Dynamsoft Barcode Reader SDK for decoding barcode and QR code, thus it is inevitable to put lots of effort into platform-specific code. Fortunately, it is not necessary to reinvent the wheel. There are some [official samples](https://github.com/xamarin/xamarin-forms-samples/tree/main/CustomRenderers) demonstrating how to use the [custom renders](https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/introduction) to bridge the shared code and platform-specific code. 

The camera preview samples include:
- [ContentPage](https://github.com/xamarin/xamarin-forms-samples/tree/main/CustomRenderers/ContentPage)
    - Camera API for Android
    - Native code takes over the whole content page rendering
- [View](https://github.com/xamarin/xamarin-forms-samples/tree/main/CustomRenderers/View)
    - Camera2 API for Android
    - Native code renders the custom view

Although Android camera API is out of date, it is still a good choice due to its simplicity. Therefore, we pick the view example as our codebase and replace the camera2 API with camera API for Android. 

Get the source code via svn command in terminal:

```bash
svn checkout https://github.com/xamarin/xamarin-forms-samples/trunk/CustomRenderers/View
```

## Implementing Xamarin.Forms Barcode QR Code Scanner
In the following paragraphs, we will show how to implement the barcode and QR code scanning feature in Xamarin.Forms. The steps include installing Dynamsoft Barcode Reader SDK, reading barcode and QR code from image file and live video stream, and drawing the results on overlay.

### Install Dynamsoft Xamarin Barcode SDK for Android and iOS
Let's open nuget package manager to search for `dynamsoft xamarin barcode` in Visual Studio.

![Xamarin barcode QR code SDK](https://www.dynamsoft.com/codepool/img/2022/07/xamarin-barcode-qr-code-sdk.png)

There are two search results: one for Android and one for iOS. Xamarin SDK is not Xamarin.Forms-compatible, so you have to install them separately for Android and iOS projects.

![Xamarin SDK installation](https://www.dynamsoft.com/codepool/img/2022/07/xamarin-sdk-installation.png)

### Content Pages
The projects consists of three content pages: main page, picture page and camera page.

The `MainPage.xaml` includes two buttons for page navigation.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
			 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
			 x:Class="BarcodeQrScanner.MainPage"
			 Title="Main Page">
	<ContentPage.Content>
		<StackLayout>
			<Button x:Name="takePhotoButton" Text="Take Photo" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" Clicked="OnTakePhotoButtonClicked" />
            <Button x:Name="takeVideoButton" Text="Take Video" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" Clicked="OnTakeVideoButtonClicked" />
		</StackLayout>
	</ContentPage.Content>
</ContentPage>

```

The `PicturePage.xaml` includes a label for displaying the barcode QR code scanning results, and a `SKCanvasView` for drawing the loaded image and overlay.

```xml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="BarcodeQrScanner.PicturePage"
             Title="Picture Page">
    <ContentPage.Content>
        <StackLayout>
            <Label FontSize="18"
                FontAttributes="Bold"
                x:Name="ResultLabel"
                   Text="Results"
                HorizontalOptions="Start"/>
            <skia:SKCanvasView x:Name="canvasView"
                           WidthRequest="640" HeightRequest="640"
                           PaintSurface="OnCanvasViewPaintSurface" />
            
        </StackLayout>
    </ContentPage.Content>
</ContentPage>
```

The `CameraPage.xaml` includes a custom camera view for live video stream, a label for barcode QR code results, and a `SKCanvasView` for overlay. The layout here is `Grid` rather than `StackLayout` because the camera view is a full screen view and other elements are placed on top of it.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
			 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:local="clr-namespace:BarcodeQrScanner;assembly=BarcodeQrScanner"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
			 x:Class="BarcodeQrScanner.CameraPage"
			 Title="Camera Page">
    <Grid x:Name="scannerView" Margin="0">
        <local:CameraPreview 
            x:Name="cameraView"
            Camera="Rear"
            ScanMode="Multiple"
			HorizontalOptions="FillAndExpand"
			VerticalOptions="FillAndExpand" 
            ResultReady="CameraPreview_ResultReady"/>
        <Label FontSize="18"
                FontAttributes="Bold"
               TextColor="Blue"
                x:Name="ResultLabel"
                   Text="Results"
                HorizontalOptions="Center"
               VerticalOptions="Center" />
        <skia:SKCanvasView x:Name="canvasView"
                           Margin="0"
                           HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
                           PaintSurface="OnCanvasViewPaintSurface" />

    </Grid>
</ContentPage>
```

### Xamarin.Forms DependencyService
Barcode reader object needs to be created in native code. To invoke native platform functionality from shared code, we use the [DependencyService](https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/dependency-service/) class.
1. In `IBarcodeQRCodeService.cs`, define an  `IBarcodeQRCodeService` interface and a `BarcodeQrData` structure. The `IBarcodeQRCodeService` interface contains a method for initializing the SDK license and a method for decoding barcode and QR code from a file. The `BarcodeQrData` structure contains the barcode and QR code results.

    ```csharp
    using SkiaSharp;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using System.Threading.Tasks;
    using Xamarin.Forms;

    namespace BarcodeQrScanner.Services
    {
        public class BarcodeQrData
        {
            public string text;
            public string format;
            public SKPoint[] points;
        }

        public interface IBarcodeQRCodeService
        {
            Task<int> InitSDK(string license);
            Task<BarcodeQrData[]> DecodeFile(string filePath);
        }
    }
    ```
2. Implement the interface in native `BarcodeQRCodeService.cs` file.

    **Android**
    ```csharp
    using Android.App;
    using Android.Content;
    using Android.OS;
    using Android.Runtime;
    using Android.Views;
    using Android.Widget;
    using Com.Dynamsoft.Dbr;
    using BarcodeQrScanner.Droid.Services;
    using BarcodeQrScanner.Services;
    using SkiaSharp;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Xamarin.Forms;

    [assembly: Dependency(typeof(BarcodeQRCodeService))]
    namespace BarcodeQrScanner.Droid.Services
    {
        public class DBRLicenseVerificationListener : Java.Lang.Object, IDBRLicenseVerificationListener
        {
            public void DBRLicenseVerificationCallback(bool isSuccess, Java.Lang.Exception error)
            {
                if (!isSuccess)
                {
                    System.Console.WriteLine(error.Message);
                }
            }
        }

        public class BarcodeQRCodeService: IBarcodeQRCodeService
        {
            BarcodeReader reader;

            Task<int> IBarcodeQRCodeService.InitSDK(string license)
            {
                BarcodeReader.InitLicense(license, new DBRLicenseVerificationListener());
                reader = new BarcodeReader();
                TaskCompletionSource<int> taskCompletionSource = new TaskCompletionSource<int>();
                taskCompletionSource.SetResult(0);
                return taskCompletionSource.Task;
            }

            Task<BarcodeQrData[]> IBarcodeQRCodeService.DecodeFile(string filePath)
            {
                BarcodeQrData[] output = null;
                try
                {
                    PublicRuntimeSettings settings = reader.RuntimeSettings;
                    settings.ExpectedBarcodesCount = 0;
                    reader.UpdateRuntimeSettings(settings);
                    TextResult[] results = reader.DecodeFile(filePath);
                    if (results != null)
                    {
                        output = new BarcodeQrData[results.Length];
                        int index = 0;
                        foreach (TextResult result in results)
                        {
                            BarcodeQrData data = new BarcodeQrData();
                            data.text = result.BarcodeText;
                            data.format = result.BarcodeFormatString;
                            LocalizationResult localizationResult = result.LocalizationResult;
                            data.points = new SKPoint[localizationResult.ResultPoints.Count];
                            int pointsIndex = 0;
                            foreach (Com.Dynamsoft.Dbr.Point point in localizationResult.ResultPoints)
                            {
                                SKPoint p = new SKPoint();
                                p.X = point.X;
                                p.Y = point.Y;
                                data.points[pointsIndex++] = p;
                            }
                            output[index++] = data;
                        }
                    }
                }
                catch (Exception e)
                {
                }

                TaskCompletionSource<BarcodeQrData[]> taskCompletionSource = new TaskCompletionSource<BarcodeQrData[]>();
                taskCompletionSource.SetResult(output);
                return taskCompletionSource.Task;
            }
        }
    }
    ```
    
    **iOS**
    ```csharp
    using System;
    using Xamarin.Forms;
    using BarcodeQrScanner.Services;
    using DBRiOS;
    using BarcodeQrScanner.iOS.Services;
    using System.Threading.Tasks;
    using Foundation;
    using SkiaSharp;

    [assembly: Dependency(typeof(BarcodeQRCodeService))]
    namespace BarcodeQrScanner.iOS.Services
    {
        public class DBRLicenseVerificationListener : NSObject, IDBRLicenseVerificationListener
        {
            public void DBRLicenseVerificationCallback(bool isSuccess, NSError error)
            {
                if (error != null)
                {
                    System.Console.WriteLine(error.UserInfo);
                }
            }
        }

        public class BarcodeQRCodeService: IBarcodeQRCodeService
        {
            DynamsoftBarcodeReader reader;

            

            Task<int> IBarcodeQRCodeService.InitSDK(string license)
            {
                DynamsoftBarcodeReader.InitLicense(license, new DBRLicenseVerificationListener());
                reader = new DynamsoftBarcodeReader();
                TaskCompletionSource<int> taskCompletionSource = new TaskCompletionSource<int>();
                taskCompletionSource.SetResult(0);
                return taskCompletionSource.Task;
            }

            Task<BarcodeQrData[]> IBarcodeQRCodeService.DecodeFile(string filePath)
            {
                BarcodeQrData[] output = null;
                try
                {
                    NSError error;

                    iPublicRuntimeSettings settings = reader.GetRuntimeSettings(out error);
                    settings.ExpectedBarcodesCount = 0;
                    reader.UpdateRuntimeSettings(settings, out error);
                    
                    iTextResult[] results = reader.DecodeFileWithName(filePath, "", out error);
                    if (results != null)
                    {
                        output = new BarcodeQrData[results.Length];
                        int index = 0;
                        foreach (iTextResult result in results)
                        {
                            BarcodeQrData data = new BarcodeQrData();
                            data.text = result.BarcodeText;
                            data.format = result.BarcodeFormatString;
                            iLocalizationResult localizationResult = result.LocalizationResult;
                            data.points = new SKPoint[localizationResult.ResultPoints.Length];
                            int pointsIndex = 0;
                            foreach (NSObject point in localizationResult.ResultPoints)
                            {
                                SKPoint p = new SKPoint();
                                p.X = (float)((NSValue)point).CGPointValue.X;
                                p.Y = (float)((NSValue)point).CGPointValue.Y;
                                data.points[pointsIndex++] = p;
                            }
                            output[index++] = data;
                        }
                    }
                }
                catch (Exception e)
                {
                }

                TaskCompletionSource<BarcodeQrData[]> taskCompletionSource = new TaskCompletionSource<BarcodeQrData[]>();
                taskCompletionSource.SetResult(output);
                return taskCompletionSource.Task;
            }

        }
    }
    ```
3. Use the `DependencyService.Get<T>` method to resolve the native implementation in `MainPage.xaml.cs`.

    ```csharp
    _barcodeQRCodeService = DependencyService.Get<IBarcodeQRCodeService>();
    await Task.Run(() =>
    {
        try
        {
            _barcodeQRCodeService.InitSDK("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==");
        }
        catch (Exception ex)
        {
            DisplayAlert("Error", ex.Message, "OK");
        }

        return Task.CompletedTask;
    });
    ```

    You can get the license key from [here](https://www.dynamsoft.com/customer/license/trialLicense?product=dbr).

### Scan Barcode and QR Code from Image File
To take pictures with the camera, we use [MediaPicker](https://docs.microsoft.com/en-us/xamarin/essentials/media-picker?tabs=android), which is provided by `Xamarin.Essentials`.

The following configurations are required for camera access on Android and iOS:
- Android `AndroidManifest.xml`:

    ```xml
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.BarcodeQrScanner" android:installLocation="auto">
        <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" />
        <application android:label="BarcodeQrScanner"></application>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <queries>
        <intent>
        <action android:name="android.media.action.IMAGE_CAPTURE" />
        </intent>
    </queries>
    </manifest>
    ```
- iOS `Info.plist`:
    ```xml
    <key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>CFBundleName</key>
	<string>BarcodeQrScanner</string>
	<key>NSCameraUsageDescription</key>
	<string>This app is using the camera</string>
	<key>NSPhotoLibraryAddUsageDescription</key>
	<string>This app is saving photo to the library</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>This app needs access to microphone for taking videos.</string>
    <key>NSPhotoLibraryAddUsageDescription</key>
    <string>This app needs access to the photo gallery for picking photos and videos.</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>This app needs access to photos gallery for picking photos and videos.</string>
    ```


We are going to call the `MediaPicker.PickPhotoAsync` method to take a photo and save then it to local disk.

```csharp
async void OnTakePhotoButtonClicked (object sender, EventArgs e)
{
    try
    {
        var photo = await MediaPicker.CapturePhotoAsync();
        await LoadPhotoAsync(photo);
        Console.WriteLine($"CapturePhotoAsync COMPLETED: {PhotoPath}");
    }
    catch (FeatureNotSupportedException fnsEx)
    {
        // Feature is not supported on the device
    }
    catch (PermissionException pEx)
    {
        // Permissions not granted
    }
    catch (Exception ex)
    {
        Console.WriteLine($"CapturePhotoAsync THREW: {ex.Message}");
    }
}

async Task LoadPhotoAsync(FileResult photo)
{
    // canceled
    if (photo == null)
    {
        PhotoPath = null;
        return;
    }
    // save the file into local storage
    var newFile = Path.Combine(FileSystem.CacheDirectory, photo.FileName);
    using (var stream = await photo.OpenReadAsync())
    using (var newStream = File.OpenWrite(newFile))
        await stream.CopyToAsync(newStream);

    PhotoPath = newFile;

    await Navigation.PushAsync(new PicturePage(PhotoPath, _barcodeQRCodeService));
}
```

Afterwards, pass the photo path and the `IBarcodeQRCodeService` instance to the `PicturePage` page for further processing.

```csharp
namespace BarcodeQrScanner
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class PicturePage : ContentPage
    {
        string path;
        SKBitmap bitmap;
        IBarcodeQRCodeService _barcodeQRCodeService;
        public PicturePage(string imagepath, IBarcodeQRCodeService barcodeQRCodeService)
        {
            InitializeComponent();
            _barcodeQRCodeService = barcodeQRCodeService;
            path = imagepath;
            try
            {
                using (var stream = new SKFileStream(imagepath))
                {
                    bitmap = SKBitmap.Decode(stream);
                }

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
    }
}
```

The `OnCanvasViewPaintSurface` method will be triggered when the element is ready to be rendered. We can draw the image bitmap and corresponding barcode and QR code results using `SKCanvas`.

```csharp
async void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    var imageCanvas = new SKCanvas(bitmap);

    SKPaint skPaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Blue,
        StrokeWidth = 10,
    };

    BarcodeQrData[] data = await _barcodeQRCodeService.DecodeFile(path);
    ResultLabel.Text = "";
    if (data != null)
    {
        foreach (BarcodeQrData barcodeQrData in data)
        {
            ResultLabel.Text += barcodeQrData.text + "\n";
            imageCanvas.DrawLine(barcodeQrData.points[0], barcodeQrData.points[1], skPaint);
            imageCanvas.DrawLine(barcodeQrData.points[1], barcodeQrData.points[2], skPaint);
            imageCanvas.DrawLine(barcodeQrData.points[2], barcodeQrData.points[3], skPaint);
            imageCanvas.DrawLine(barcodeQrData.points[3], barcodeQrData.points[0], skPaint);
        }
    }
    else
    {
        ResultLabel.Text = "No barcode QR code found";
    }

    float scale = Math.Min((float)info.Width / bitmap.Width,
                        (float)info.Height / bitmap.Height);
    float x = (info.Width - scale * bitmap.Width) / 2;
    float y = (info.Height - scale * bitmap.Height) / 2;
    SKRect destRect = new SKRect(x, y, x + scale * bitmap.Width,
                                        y + scale * bitmap.Height);

    canvas.DrawBitmap(bitmap, destRect);
}
```

![Xamarin.Forms barcode QR code reader](https://www.dynamsoft.com/codepool/img/2022/07/xamarin-forms-barcode-qrcode-reader.jpg)

### Scan Barcode and QR Code from Live Video Stream
Dynamsoft Barcode Reader supports multiple barcode and QR code detection. So we add a new property to the pre-created `CameraPreview.cs` file.

```csharp
public enum ScanOptions
{
    Single,
    Multiple
}

public static readonly BindableProperty ScanProperty = BindableProperty.Create(
            propertyName: "ScanMode",
            returnType: typeof(ScanOptions),
            declaringType: typeof(CameraPreview),
            defaultValue: ScanOptions.Single);

public ScanOptions ScanMode
{
    get { return (ScanOptions)GetValue(ScanProperty); }
    set { SetValue(ScanProperty, value); }
}
```

To pass results from native code to shared code, we create a event handler and a callback function. The video frame width and height are used to calculate the scale factor for overlay drawing.

```csharp
public class ResultReadyEventArgs : EventArgs
{
    public ResultReadyEventArgs(object result, int previewWidth, int previewHeight)
    {
        Result = result;
        PreviewWidth = previewWidth;
        PreviewHeight = previewHeight;
    }

    public object Result { get; private set; }
    public int PreviewWidth { get; private set; }
    public int PreviewHeight { get; private set; }

}

public event EventHandler<ResultReadyEventArgs> ResultReady;

public void NotifyResultReady(object result, int previewWidth, int previewHeight)
{
    if (ResultReady != null)
    {
        ResultReady(this, new ResultReadyEventArgs(result, previewWidth, previewHeight));
    }
}
```

The `CameraPreviewRenderer.cs` file is the entry point of native code. We need to first add the code logic for receiving the video frames.

**Android**
```csharp
public class CameraPreviewRenderer : FrameLayout, IVisualElementRenderer, IViewRenderer, TextureView.ISurfaceTextureListener, IPreviewCallback, Handler.ICallback
{
    public void OnPreviewFrame(byte[] data, Android.Hardware.Camera camera)
    {
        try
        {
            YuvImage yuvImage = new YuvImage(data, ImageFormatType.Nv21,
                    previewWidth, previewHeight, null);
            stride = yuvImage.GetStrides();
            try
            {
                if (isReady)
                {
                    if (backgroundHandler != null)
                    {
                        isReady = false;
                        Message msg = new Message();
                        msg.What = 100;
                        msg.Obj = yuvImage;
                        backgroundHandler.SendMessage(msg);
                    }
                }
            }
            catch (BarcodeReaderException e)
            {
                e.PrintStackTrace();
            }
        }
        catch (System.IO.IOException)
        {
        }
    }

    void PrepareAndStartCamera()
    {
        camera.SetPreviewCallback(null);
        camera.StopPreview();

        var display = activity.WindowManager.DefaultDisplay;
        if (display.Rotation == SurfaceOrientation.Rotation0)
        {
            camera.SetDisplayOrientation(90);
        }

        if (display.Rotation == SurfaceOrientation.Rotation270)
        {
            camera.SetDisplayOrientation(180);
        }

        Parameters parameters = camera.GetParameters();
        previewWidth = parameters.PreviewSize.Width;
        previewHeight = parameters.PreviewSize.Height;
        if (parameters.SupportedFocusModes.Contains(Parameters.FocusModeContinuousVideo))
        {
            parameters.FocusMode = Parameters.FocusModeContinuousVideo;
        }
        camera.SetParameters(parameters);
        camera.SetPreviewCallback(this);
        camera.StartPreview();
    }
}
```

**iOS**

```csharp
class CaptureOutput : AVCaptureVideoDataOutputSampleBufferDelegate
{
    ...

    public override void DidOutputSampleBuffer(AVCaptureOutput captureOutput, CMSampleBuffer sampleBuffer, AVCaptureConnection connection)
    {
        if (ready)
        {
            ready = false;
            CVPixelBuffer cVPixelBuffer = (CVPixelBuffer)sampleBuffer.GetImageBuffer();

            cVPixelBuffer.Lock(CVPixelBufferLock.ReadOnly);
            nint dataSize = cVPixelBuffer.DataSize;
            width = cVPixelBuffer.Width;
            height = cVPixelBuffer.Height;
            IntPtr baseAddress = cVPixelBuffer.BaseAddress;
            bpr = cVPixelBuffer.BytesPerRow;
            cVPixelBuffer.Unlock(CVPixelBufferLock.ReadOnly);
            buffer = NSData.FromBytes(baseAddress, (nuint)dataSize);
            cVPixelBuffer.Dispose();
            queue.DispatchAsync(ReadTask);
        }
        sampleBuffer.Dispose();
    }
    ...
}

void Initialize()
{
    CaptureSession = new AVCaptureSession();
    previewLayer = new AVCaptureVideoPreviewLayer(CaptureSession)
    {
        Frame = Bounds,
        VideoGravity = AVLayerVideoGravity.ResizeAspectFill
    };

    var videoDevices = AVCaptureDevice.DevicesWithMediaType(AVMediaType.Video);
    var cameraPosition = (cameraOptions == CameraOptions.Front) ? AVCaptureDevicePosition.Front : AVCaptureDevicePosition.Back;
    var device = videoDevices.FirstOrDefault(d => d.Position == cameraPosition);

    if (device == null)
    {
        return;
    }

    NSError error;

    iPublicRuntimeSettings settings = reader.GetRuntimeSettings(out error);
    settings.ExpectedBarcodesCount = (cameraPreview.ScanMode == ScanOptions.Single) ? 1 : 0;
    reader.UpdateRuntimeSettings(settings, out error);
    
    var input = new AVCaptureDeviceInput(device, out error);
    CaptureSession.AddInput(input);
    var videoDataOutput = new AVCaptureVideoDataOutput()
    {
        AlwaysDiscardsLateVideoFrames = true
    };
    if (CaptureSession.CanAddOutput(videoDataOutput))
    {
        CaptureSession.AddOutput(videoDataOutput);
        captureOutput.reader = reader;
        captureOutput.update = UpdateResults;

        DispatchQueue queue = new DispatchQueue("camera");
        videoDataOutput.SetSampleBufferDelegateQueue(captureOutput, queue);
        videoDataOutput.WeakVideoSettings = new NSDictionary<NSString, NSObject>(CVPixelBuffer.PixelFormatTypeKey, NSNumber.FromInt32((int)CVPixelFormatType.CV32BGRA));
    }
    CaptureSession.CommitConfiguration();

    Layer.AddSublayer(previewLayer);
    CaptureSession.StartRunning();
    IsPreviewing = true;
}
```

After receiving an video frame, we use a background thread to process the frame and get the results.

**Android**
```csharp
BarcodeQrData[] output = null;
try
{
    YuvImage image = (YuvImage)msg.Obj;
    if (image != null)
    {
        int[] stridelist = image.GetStrides();
        TextResult[] results = barcodeReader.DecodeBuffer(image.GetYuvData(), previewWidth, previewHeight, stridelist[0], EnumImagePixelFormat.IpfNv21);
        if (results != null && results.Length > 0)
        {
            output = new BarcodeQrData[results.Length];
            int index = 0;
            foreach (TextResult result in results)
            {
                BarcodeQrData data = new BarcodeQrData();
                data.text = result.BarcodeText;
                data.format = result.BarcodeFormatString;
                LocalizationResult localizationResult = result.LocalizationResult;
                data.points = new SKPoint[localizationResult.ResultPoints.Count];
                int pointsIndex = 0;
                foreach (Com.Dynamsoft.Dbr.Point point in localizationResult.ResultPoints)
                {
                    SKPoint p = new SKPoint();
                    p.X = point.X;
                    p.Y = point.Y;
                    data.points[pointsIndex++] = p;
                }
                output[index++] = data;
            }
        }
    }
}
catch (BarcodeReaderException e)
{
    e.PrintStackTrace();
}
```

**iOS**
```csharp
output = null;
if (reader != null)
{
    results = reader.DecodeBuffer(buffer,
                                width,
                                height,
                                bpr,
                                EnumImagePixelFormat.Argb8888,
                                "", out errorr);

    if (results != null && results.Length > 0)
    {
        output = new BarcodeQrData[results.Length];
        int index = 0;
        foreach (iTextResult result in results)
        {
            BarcodeQrData data = new BarcodeQrData();
            data.text = result.BarcodeText;
            data.format = result.BarcodeFormatString;
            iLocalizationResult localizationResult = result.LocalizationResult;
            data.points = new SKPoint[localizationResult.ResultPoints.Length];
            int pointsIndex = 0;
            foreach (NSObject point in localizationResult.ResultPoints)
            {
                SKPoint p = new SKPoint();
                p.X = (float)((NSValue)point).CGPointValue.X;
                p.Y = (float)((NSValue)point).CGPointValue.Y;
                data.points[pointsIndex++] = p;
            }
            output[index++] = data;
        }
    }
    else
    {
        result = "";
    }
}
```

Finally, call `NotifyResultReady` to send the results to the shared code.

**Android**
```csharp
Element.NotifyResultReady(output, previewWidth, previewHeight);
```

**iOS**
```csharp
cameraPreview.NotifyResultReady(captureOutput.output, (int)captureOutput.width, (int)captureOutput.height);
```

When the results reach the camera page, we draw the results on `SKCanvasView`. The pixel density is vital for coordinate transformation.

```csharp
private void CameraPreview_ResultReady(object sender, ResultReadyEventArgs e)
{
    if (e.Result != null)
    {
        data = (BarcodeQrData[])e.Result;
    }
    else
    {
        data = null;
    }

    imageWidth = e.PreviewWidth;
    imageHeight = e.PreviewHeight;

    canvasView.InvalidateSurface();
}

public static SKPoint rotateCW90(SKPoint point, int width)
{
    SKPoint rotatedPoint = new SKPoint();
    rotatedPoint.X = width - point.Y;
    rotatedPoint.Y = point.X;
    return rotatedPoint;
}

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    double width = canvasView.Width;
    double height = canvasView.Height;

    var mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
    var orientation = mainDisplayInfo.Orientation;
    var rotation = mainDisplayInfo.Rotation;
    var density = mainDisplayInfo.Density;

    width *= density;
    height *= density;

    double scale, widthScale, heightScale, scaledWidth, scaledHeight;

    if (orientation == DisplayOrientation.Portrait)
    {
        widthScale = imageHeight / width;
        heightScale = imageWidth / height;
        scale = widthScale < heightScale ? widthScale : heightScale;
        scaledWidth = imageHeight / scale;
        scaledHeight = imageWidth / scale;
    }
    else
    {
        widthScale = imageWidth / width;
        heightScale = imageHeight / height;
        scale = widthScale < heightScale ? widthScale : heightScale;
        scaledWidth = imageWidth / scale;
        scaledHeight = imageHeight / scale;
    }

    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    SKPaint skPaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Blue,
        StrokeWidth = 10,
    };

    SKPaint textPaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Red,
        TextSize = (float)(18 * density),
        StrokeWidth = 4,
    };

    ResultLabel.Text = "";
    if (data != null)
    {
        foreach (BarcodeQrData barcodeQrData in data)
        {
            for (int i = 0; i < 4; i++)
            {
                if (orientation == DisplayOrientation.Portrait)
                {
                    barcodeQrData.points[i] = rotateCW90(barcodeQrData.points[i], imageHeight);
                }

                if (widthScale < heightScale)
                {
                    barcodeQrData.points[i].X = (float)(barcodeQrData.points[i].X / scale);
                    barcodeQrData.points[i].Y = (float)(barcodeQrData.points[i].Y / scale - (scaledHeight - height) / 2);
                }
                else
                {
                    barcodeQrData.points[i].X = (float)(barcodeQrData.points[i].X / scale - (scaledWidth - width) / 2);
                    barcodeQrData.points[i].Y = (float)(barcodeQrData.points[i].Y / scale);
                }
            }

            canvas.DrawText(barcodeQrData.text, barcodeQrData.points[0], textPaint);
            canvas.DrawLine(barcodeQrData.points[0], barcodeQrData.points[1], skPaint);
            canvas.DrawLine(barcodeQrData.points[1], barcodeQrData.points[2], skPaint);
            canvas.DrawLine(barcodeQrData.points[2], barcodeQrData.points[3], skPaint);
            canvas.DrawLine(barcodeQrData.points[3], barcodeQrData.points[0], skPaint);
        }
    }
    else
    {
        ResultLabel.Text = "No barcode QR code found";
    }
}
```

Now the Xamarin.Forms barcode QR code scanner is ready to use.

![Xamarin.Forms barcode QR code scanner](https://www.dynamsoft.com/codepool/img/2022/07/xamarin-forms-barcode-qrcode-scanner.jpg)

## Source Code
[https://github.com/yushulx/xamarin-forms-barcode-qrcode-scanner](https://github.com/yushulx/xamarin-forms-barcode-qrcode-scanner)