Search code examples
c#qr-codewebcamzxingwinui-3

How to scan a QR Code in WinUI 3 using webcam?


I'm trying to scan a QR-Code from WebCam in WinUI 3. I've installed ZXing and AForge Video but these libraries doesn't work properly in WinUI 3. Are there any solutions or more compatible alternatives?


Solution

  • ZXing (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":

    enter image description here