銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

UWP でもバーコードスキャン機能を実装したい

先日のエントリの最後に触れていた話です。

tech.tanaka733.net

試してみると実はそこまで難しくなくバーコード(QRコード)を読み取ることができました。簡単になった最大の理由は、Microsoftが提供しているUWPのサンプルです。

github.com

カメラを使ってPreviewFrameを取得するサンプルアプリがあります。ここにあるコードを必要なところだけコピーしてきたらほぼ終わりです。このとき、カメラを使うためにはアプリの機能でカメラマイクを有効にする必要があります。また、上のサンプルにあるように撮影した画像を保存する場合はピクチャライブラリなど保存先へのアクセスが必要です。今回のバーコードリーダーのサンプルでは保存処理は除外し、インメモリで処理するため、保存先へのアクセスは必要ありません。

f:id:tanaka733:20151206012417p:plain

次に、UWPでも使えるZXing.Net(mobileではない方)を追加します。project.json のdependencies の中に次の行を追加します。

  "dependencies": {
    "ZXing.Net": "0.14.0.1"
  },

そして肝心のコードですが、1秒おきにフォーカスを合わせて写っている画像をQRCodeとして読み取りを試みるようにしました。

private async Task TryDecodePreviewAsync()
{
    await mediaCapture.VideoDeviceController.FocusControl.FocusAsync();
    // Get information about the preview
    var previewProperties = mediaCapture.VideoDeviceController.GetMediaStreamProperties(MediaStreamType.VideoPreview) as VideoEncodingProperties;
    if (previewProperties == null)
        return;
    // このVideoFrameのFormatとBarcodeReaderのフォーマットで一致するのがGray8しかない
    var videoFrame = new VideoFrame(BitmapPixelFormat.Gray8, (int)previewProperties.Width, (int)previewProperties.Height);

    // Capture the preview frame
    using (var currentFrame = await mediaCapture.GetPreviewFrameAsync(videoFrame))
    {
        // 結果フレームを取得
        var previewFrame = currentFrame.SoftwareBitmap;
        // 結果をbyte配列に変換。DecodeメソッドはWriteableBitmapも受け付けるが、
        // こちらはUIスレッド上でないと生成できないのであきらめる。
        var buffer = new byte[4 * previewFrame.PixelWidth * previewFrame.PixelHeight];
        previewFrame.CopyToBuffer(buffer.AsBuffer());
        var barcodeReader = new BarcodeReader {AutoRotate = true};
        var r = barcodeReader.Decode(buffer, previewFrame.PixelWidth, previewFrame.PixelHeight, BitmapFormat.Gray8);
        Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            FrameInfoTextBlock.Text = string.Format("{0}x{1} {2}", previewFrame.PixelWidth, previewFrame.PixelHeight, previewFrame.BitmapPixelFormat);
            ResultText.Text = r?.Text ?? "";
        }).FireAndForget();
    }
}

FireAndForget は拡張メソッドです。

public static class TaslExtensions
{
    public static void FireAndForget(this Task task)
    {
        task.ContinueWith(t => { Debug.WriteLine(t.Exception);}, TaskContinuationOptions.OnlyOnFaulted);
    }

    public static void FireAndForget(this IAsyncAction action)
    {
        action.AsTask().FireAndForget();
    }
}

previewFrameを取得するまではサンプルそのままで、そこからZXing.Netを使います。コメントにあるように、BarcodeReaderに渡す画像データはWriteableBitmapか、byte配列に付加情報を追加したものが選べるのですが、WriteableBitmapはUIスレッドから生成しないといけないので、previewFrameをbyte配列に変換しています。

実行するとこんな感じです。

f:id:tanaka733:20151206013953p:plain

実行したサンプル一式をGitHubに公開しています。

github.com