How to Build Xamarin.Forms Barcode QR Code Scanner

yushulx

Xiao Ling

Posted on July 7, 2022

How to Build Xamarin.Forms Barcode QR Code Scanner

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 and Dynamsoft Barcode Reader SDK.

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 demonstrating how to use the custom renders to bridge the shared code and platform-specific code.

The camera preview samples include:

  • ContentPage
    • Camera API for Android
    • Native code takes over the whole content page rendering
  • 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:

svn checkout https://github.com/xamarin/xamarin-forms-samples/trunk/CustomRenderers/View
Enter fullscreen mode Exit fullscreen mode

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

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

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 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>

Enter fullscreen mode Exit fullscreen mode

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 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>
Enter fullscreen mode Exit fullscreen mode

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 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>
Enter fullscreen mode Exit fullscreen mode

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

    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

    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

    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.

    _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.

Scan Barcode and QR Code from Image File

To take pictures with the camera, we use MediaPicker, which is provided by Xamarin.Essentials.

The following configurations are required for camera access on Android and iOS:

  • Android AndroidManifest.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:

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

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));
}
Enter fullscreen mode Exit fullscreen mode

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

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);
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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);
}
Enter fullscreen mode Exit fullscreen mode

Xamarin.Forms barcode QR code reader

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.

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); }
}
Enter fullscreen mode Exit fullscreen mode

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.

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));
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

iOS

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;
}
Enter fullscreen mode Exit fullscreen mode

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

Android

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();
}
Enter fullscreen mode Exit fullscreen mode

iOS

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 = "";
    }
}
Enter fullscreen mode Exit fullscreen mode

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

Android

Element.NotifyResultReady(output, previewWidth, previewHeight);
Enter fullscreen mode Exit fullscreen mode

iOS

cameraPreview.NotifyResultReady(captureOutput.output, (int)captureOutput.width, (int)captureOutput.height);
Enter fullscreen mode Exit fullscreen mode

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

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";
    }
}
Enter fullscreen mode Exit fullscreen mode

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

Xamarin.Forms barcode QR code scanner

Source Code

https://github.com/yushulx/xamarin-forms-barcode-qrcode-scanner

💖 💪 🙅 🚩
yushulx
Xiao Ling

Posted on July 7, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related