How To Scan QRCode For UWP (4)

QR Code的全称是Quick Response Code,中文翻译为快速响应矩阵图码,有关它的简介可以查看维基百科。 我准备使用ZXing.Net来实现扫描二维码的功能,ZXing.Net在Codeplex上有介绍可以参考https://zxingnet.codeplex.com/。

可以通过Nuget来引入ZXing.uwp到项目中,解码QR code的API非常简单,如下所示:

public Result Decode(WriteableBitmap barcodeBitmap);

看上去实现解码功能很easy,只需要调用 LowLagPhotoCapture.CaptureAsync 获取Camera捕获的IRandomAccessStream对象,然后构造一个新的 WriteableBitmap 对象 调用WriteableBmp.SetSourceAsync 将捕获的流设置到 WriteableBitmap 里面。最后调用BarcodeReader.Decode来解码QR code。

然而事情往往并非那么顺利,拿着平板对着屏幕扫了半天,也不见能够Quick Response。看来下ZXing.Net写的实例代码,跟我写的没有啥区别,同样也是很难解码QR code。 接着我尝试去解码一个静态的二维码图片,发现成功率100%,而且成功解码出来都是毫秒级别的。于是我又尝试去调试了下Decode的实现源码,发现我拍照的图片分辨率是1920 * 1080,那张静态图片的分辨率是300 * 300。 于是我需要做的就是将拍照的图片进行裁剪,裁剪出来的图片的大小跟中间的那个框的大小差不多。关于如何去裁剪图片可以参考另外一片随笔 How To Crop Bitmap For UWP

为了能方便测试反复扫描二维码,我还写了一个简单的ResultPage,就一个文本框显示解码后的文本,一个按钮点击继续扫描。

实现代码也是非常简单:

 1 using Windows.UI.Xaml;
 2 using Windows.UI.Xaml.Controls;
 3 using Windows.UI.Xaml.Navigation;
 4 
 5 namespace ScanQRCode
 6 {
 7     public sealed partial class ResultPage : Page
 8     {
 9         public ResultPage()
10         {
11             this.InitializeComponent();
12         }
13 
14         protected override void OnNavigatedTo(NavigationEventArgs e)
15         {
16             txtResult.Text = e.Parameter.ToString();
17         }
18 
19         private void btnScan_Click(object sender, RoutedEventArgs e)
20         {
21             Frame.Navigate(typeof(MainPage));
22         }
23     }
24 }

 扫描二维码的具体实现代码如下:

private async Task StartScanQRCode()
        {
            try
            {
                Result _result = null;
                while (_result == null && lowLagPhotoCapture != null && IsCurrentUIActive)
                {
                    var capturedPhoto = await lowLagPhotoCapture.CaptureAsync();
                    if (capturedPhoto == null)
                    {
                        continue;
                    }
                    using (var stream = capturedPhoto.Frame.CloneStream())
                    {
                        //calculate the crop square's length.
                        var pixelWidth = capturedPhoto.Frame.Width;
                        var pixelHeight = capturedPhoto.Frame.Height;
                        var rate = Math.Min(pixelWidth, pixelHeight) / Math.Min(ActualWidth, ActualHeight);
                        var cropLength = focusRecLength * rate;

                        // initialize with 1,1 to get the current size of the image
                        var writeableBmp = new WriteableBitmap(1, 1);
                        var rect = new Rect(pixelWidth / 2 - cropLength / 2,
                            pixelHeight / 2 - cropLength / 2, cropLength, cropLength);
                        using (var croppedStream = await ImageHelper.GetCroppedStreamAsync(stream, rect))
                        {
                            writeableBmp.SetSource(croppedStream);
                            // and create it again because otherwise the WB isn't fully initialized and decoding
                            // results in a IndexOutOfRange
                            writeableBmp = new WriteableBitmap((int)cropLength, (int)cropLength);
                            croppedStream.Seek(0);
                            await writeableBmp.SetSourceAsync(croppedStream);
                        }

                        _result = ScanBitmap(writeableBmp);
                    }
                }

                if (_result != null)
                {
                    Frame.Navigate(typeof(ResultPage), _result.Text);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Exception when scaning QR code" + ex.Message);
            }
        }

 private Result ScanBitmap(WriteableBitmap writeableBmp)
        {
            var barcodeReader = new BarcodeReader
            {
                AutoRotate = true,
                Options = { TryHarder = true }
            };

            return barcodeReader.Decode(writeableBmp);
        }

 

在处理窗体VisibilityChanged/Application.Current.Suspending/OnNavigatedTo/OnNavigatedFrom事件引发的时候,需要很好控制是需要开启Camera还是要关闭销毁Camera。本示例主要靠IsCurrentUIActive属性来判断,当IsCurrentUIActive为false的时候不能去调用LowLagPhotoCapture.CaptureAsync,否则就会抛“ A media source cannot go from the stopped state to the paused state ”异常,之后又无法调用MediaCapture.StopPreviewAsync来停止预览。重新初始化MediaCapture,再次去调用LowLagPhotoCapture.CaptureAsync又会抛出“ Hardware MFT failed to start streaming due to lack of hardware resources ” 异常,因为之前没有停止。这个问题困扰了我一段时间。

实现代码:

private bool IsCurrentUIActive
        {
            // UI is active if
            // * We are the current active page.
            // * The window is visible.
            // * The app is not suspending.
            get { return _isActivePage && !_isSuspending && Window.Current.Visible; }
        }

根据当前UI是否为Active来决定是启动Camera还是销毁Camera。

private async Task SwitchCameraOnUIStateChanged()
        {
            // Avoid reentrancy: Wait until nobody else is in this function.
            while (!_setupTask.IsCompleted)
            {
                await _setupTask;
            }

            if (_isUIActive != IsCurrentUIActive)
            {
                _isUIActive = IsCurrentUIActive;

                Func<Task> setupAsync = async () =>
                {
                    if (IsCurrentUIActive)
                    {
                        await StartCameraAsync();
                    }
                    else
                    {
                        await CleanupCameraAsync();
                    }
                };
                _setupTask = setupAsync();
            }

            await _setupTask;
        }

 

完整代码已上传到github

https://github.com/supperwu/UWP_Simple/tree/master/ScanQRCode

 

posted @ 2017-08-03 16:29  supperwu  阅读(756)  评论(0编辑  收藏  举报