How to scan a QR Code in WinUI 3 using webcam?
See the question and my original answer on StackOverflowZXing (ZXing.NET is a port) is completely platform agnostic, so it works fine with any technology as long as you can capture a "bitmap" (RGB, etc.) of some kind whatever that means on a given platform.
Here is WinUI3 (make sure you have the latest WinUI3 nugets installed) sample application that does two things:
- It captures the (first on your PC) webcam outputs and displays it in a WinUI3 MediaPlayerElement.
- For each frame, it runs ZXing barcode reader decoding, trying to find a QR Code, and it displays it when it finds one.
The code could be easily changed to read any barcode (EAN13, etc.) that XZing supports. It contains a thin adaptation layer between XZing and WinRT's SoftwareBitmap (that I have copied from XZing.NET code):
Here is the XAML for a WinUI3 page:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Click="Button_Click">Toggle Capture</Button>
<TextBox x:Name="textBox" Grid.Row="1" />
<MediaPlayerElement
x:Name="player"
Grid.Row="2"
Width="600"
Height="600"
AutoPlay="True" />
</Grid>
And the code in a WinUI3 page (you need the XZing.NET nuget package installed):
public sealed partial class MainPage : Page
{
private readonly SoftwareBitmapBarcodeReader _reader;
private MediaCapture _capture;
private MediaFrameReader _frameReader;
private MediaSource _mediaSource;
public MainPage()
{
InitializeComponent();
// set various xzing options (beware, all formats like All_1D can divide perf by orders of magnitude)
_reader = new SoftwareBitmapBarcodeReader
{
AutoRotate = true
};
_reader.Options.PossibleFormats = new[] { BarcodeFormat.QR_CODE };
_reader.Options.TryHarder = true;
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
if (_capture == null)
{
await InitializeCaptureAsync();
return;
}
await TerminateCaptureAsync();
}
private async Task InitializeCaptureAsync()
{
// get first capture device (change this if you want)
var sourceGroup = (await MediaFrameSourceGroup.FindAllAsync())?.FirstOrDefault();
if (sourceGroup == null)
return; // not found!
// init capture & initialize
_capture = new MediaCapture();
await _capture.InitializeAsync(new MediaCaptureInitializationSettings
{
SourceGroup = sourceGroup,
SharingMode = MediaCaptureSharingMode.SharedReadOnly,
MemoryPreference = MediaCaptureMemoryPreference.Cpu, // to ensure we get SoftwareBitmaps
});
// initialize source
var source = _capture.FrameSources[sourceGroup.SourceInfos[0].Id];
// create reader to get frames & pass reader to player to visualize the webcam
_frameReader = await _capture.CreateFrameReaderAsync(source, MediaEncodingSubtypes.Bgra8);
_frameReader.FrameArrived += OnFrameArrived;
await _frameReader.StartAsync();
_mediaSource = MediaSource.CreateFromMediaFrameSource(source);
player.Source = _mediaSource;
}
private void OnFrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
{
var bmp = sender.TryAcquireLatestFrame()?.VideoMediaFrame?.SoftwareBitmap;
if (bmp == null)
return;
var result = _reader.Decode(bmp);
if (result != null)
{
// found a QR CODE
DispatcherQueue.TryEnqueue(() =>
{
textBox.Text = result.BarcodeFormat + ": " + result.Text;
});
}
}
private async Task TerminateCaptureAsync()
{
player.Source = null;
_mediaSource?.Dispose();
_mediaSource = null;
if (_frameReader != null)
{
_frameReader.FrameArrived -= OnFrameArrived;
await _frameReader.StopAsync();
_frameReader?.Dispose();
_frameReader = null;
}
_capture?.Dispose();
_capture = null;
}
}
// this is the thin layer that allows you to use XZing over WinRT's SoftwareBitmap
public class SoftwareBitmapBarcodeReader : BarcodeReader<SoftwareBitmap>
{
public SoftwareBitmapBarcodeReader()
: base(bmp => new SoftwareBitmapLuminanceSource(bmp))
{
}
}
// from https://github.com/micjahn/ZXing.Net/blob/master/Source/lib/BitmapLuminanceSource.SoftwareBitmap.cs
public class SoftwareBitmapLuminanceSource : BaseLuminanceSource
{
protected SoftwareBitmapLuminanceSource(int width, int height)
: base(width, height)
{
}
public SoftwareBitmapLuminanceSource(SoftwareBitmap softwareBitmap)
: base(softwareBitmap.PixelWidth, softwareBitmap.PixelHeight)
{
if (softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Gray8)
{
using SoftwareBitmap convertedSoftwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Gray8);
convertedSoftwareBitmap.CopyToBuffer(luminances.AsBuffer());
return;
}
softwareBitmap.CopyToBuffer(luminances.AsBuffer());
}
protected override LuminanceSource CreateLuminanceSource(byte[] newLuminances, int width, int height)
=> new SoftwareBitmapLuminanceSource(width, height) { luminances = newLuminances };
}
And here is the result on a QR Code that encodes "Hello World":