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