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
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.
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.
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>
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>
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>
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.
-
In
IBarcodeQRCodeService.cs
, define anIBarcodeQRCodeService
interface and aBarcodeQrData
structure. TheIBarcodeQRCodeService
interface contains a method for initializing the SDK license and a method for decoding barcode and QR code from a file. TheBarcodeQrData
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); } }
-
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; } } }
-
Use the
DependencyService.Get<T>
method to resolve the native implementation inMainPage.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));
}
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);
}
}
}
}
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);
}
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); }
}
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));
}
}
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();
}
}
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;
}
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();
}
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 = "";
}
}
Finally, call NotifyResultReady
to send the results to the shared code.
Android
Element.NotifyResultReady(output, previewWidth, previewHeight);
iOS
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.
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.
Source Code
https://github.com/yushulx/xamarin-forms-barcode-qrcode-scanner