アニメーションQRコードでデータを転送する


QRコードは、異なるデバイス間でデータを転送するのに便利です.Androidデバイスは、QRコードを介してIOSデバイスとデータを交換することができます.それはワイヤレスネットワークを設定する必要はありませんし、Bluetoothを使用してペアリングの面倒を持っていません.それはスクリーンカメラのソリューションですので、それも安全に使用することです.
で論じられるようにprevious article , QRコードは、そのバージョンが40に設定され、そのエラー訂正レベルがバイトモードで低く設定された状態で2953バイトまで格納できます.これは、Webリンクやドライバのライセンス情報などのデータを転送するのに十分です.
どのような場合は、QRコードを使用してより多くのデータを転送したいですか?
このデータをいくつかのqr符号に分離し,これらの符号を復号することによりデータ全体を取り出すことができる.我々は、1つの紙にこれらのコードを印刷したり、画面上にアニメーション化することができます.The Dynamsoft Barcode Reader (DBR) つのイメージで複数のコードを読んで、ビデオストリームから読むことができます.
この記事では、我々はアニメーションQRコードでデータを転送に焦点を当てている.
これについての研究があり、そのうちの1つはTXQR . これは、データに冗長性を追加するには、いくつかのフレームをスキップすると、読書に影響を与えませんFuntainコードを使用します.これは、噴水コードに向ける前に繰り返しコードモードを使用します.プログラムはGOで実装されます.
しかし、Funtainコードを使用して、プロセスに余分な複雑さをもたらし、実際には、良いバーコードのSDKを読んで、繰り返しモードも満足しているレートを持つことができます.次の部分では、我々は、アニメーションのQRコードジェネレータと純粋なJavaScriptでアニメーションQRコードリーダーを作成する予定です.ユーザーが転送し、別のデバイス上のQRコードをスキャンしてそれを取得するファイルを選択することができます.

アニメーションのQRコードジェネレータ
転送するファイルはまずバイトとして読み取り、チャンクに固定されたチャンクに分割されます.メタデータは各チャンクの開始に追加されます.メタデータは簡単です.チャンクのインデックスとチャンクの総数とチャンクが最初の場合、ファイル名とMIMEタイプも同様に追加されます.
チャンクの例:
  • "1/11 "
  • "2/11 "
  • "3月11日バイト"
  • チャンクを作成した後、JavaScript library QRコードを生成し、ループでアニメーション化する.
    ユーザーは、受信機をより良い読書を助けるためにQRコードのチャンクサイズと間隔を調整することができます.
    ここにコードの主要な部分があります.
    <input type="file" id="file" onchange="loadfile()"/>
    <label for="name">Chunk size (bytes):</label>
    <input type="text" id="chunkSize" name="chunkSize" value="2000">
    <label for="name">Extra interval (ms):</label>
    <input type="text" id="interval" name="interval" value="200">
    <div id="placeHolder"></div>
    <script>
    qrcode.stringToBytes = function(data) { return data; }; //store bytes directly
    function loadfile() { 
        let files = document.getElementById('file').files;
        if (files.length == 0) {
            return;
        }
        var file = files[0];
        fileReader = new FileReader();
        fileReader.onload = function(e){
            loadArrayBufferToChunks(e.target.result,file.name,file.type);
            showAnimatedQRCode();
        };
        fileReader.onerror = function () {
            console.warn('oops, something went wrong.');
        };
        fileReader.readAsArrayBuffer(file);
    }
    
    function loadArrayBufferToChunks(bytes,filename,type){
        var bytes = new Uint8Array(bytes);
        var data = concatTypedArrays(stringToBytes(encodeURIComponent(filename)+"|"+type+"|"),bytes); //The filename is encoded for non-ascii characters like Chinese.
        var chunkSize = parseInt(document.getElementById("chunkSize").value);
        var num = Math.ceil(data.length / chunkSize)
        chunks=[];
        for (var i=0;i<num;i++){
            var start = i*chunkSize;
            var chunk = data.slice(start,start+chunkSize);
            var meta = (i+1)+"/"+num+"|";
            chunk = concatTypedArrays(stringToBytes(meta),chunk);
            chunks.push(chunk);
        }
    }
    
    function showAnimatedQRCode(){
        createQRCode(chunks[currentIndex]);
        currentIndex = currentIndex + 1
        var interval = parseInt(document.getElementById("interval").value)
        setTimeout("showAnimatedQRCode()",interval);
    }
    
    function createQRCode(data){
        var typeNumber = 0;
        var errorCorrectionLevel = 'L';
        var qr = qrcode(typeNumber, errorCorrectionLevel);
        qr.addData(data);
        qr.make();
        var placeHolder = document.getElementById('placeHolder');
        placeHolder.innerHTML = qr.createSvgTag(); // or createImgTag
    }
    
    function stringToBytes(s) {
        var bytes = [];
        for (var i = 0; i < s.length; i += 1) {
            var c = s.charCodeAt(i);
            bytes.push(c & 0xff);
        }
        return bytes;
    }
    
    //https://stackoverflow.com/questions/33702838/how-to-append-bytes-multi-bytes-and-buffer-to-arraybuffer-in-javascript
    function concatTypedArrays(a, b) { //array + unint8 array
        var newLength = a.length + b.byteLength;
        console.log(newLength);
        var c = new Uint8Array(newLength);
        c.set(a, 0);
        c.set(b, a.length);
        return c;
    }
    </script>
    

    アニメーションのQRコードリーダー
    現在、我々は発電機を持っています.我々は、アニメーションのQRコードを読むために読者が必要です.我々は、Dynamsoftバーコードリーダーのを使用して、高性能でネイティブのモバイルアプリケーションを作成することができますmobile SDK . しかし、使いやすさとクロスプラットフォームの懸念のために、ここでは、我々は使用するつもりですJavaScript version DBRのリーダーを作成します.

    ビデオストリームからQRコードを読む
    DBRのJavaScriptバージョンは使いやすいです.次のコードを使用してビデオストリームからQRコードを読み始めることができます.
    <!DOCTYPE html>
    <html>
    <head>
        <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" />
        <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dbr.js"></script>
    </head>
    <body>
    <input type="button" value="Start Scanning" onclick="startScanning();" />
    <script>
    let scanner = null;
    init();
    async function init(){
        scanner = await Dynamsoft.DBR.BarcodeScanner.createInstance();
        scanner.onFrameRead = results => {
            //handle barcode results
        };
        scanner.onUnduplicatedRead = (txt, result) => {
        };
    }
    
    async function startScanning(){
        if (scanner==null){
            alert("Please wait for the initialization of the scanner.")
            return;
        }
        await scanner.show();
    }
    </script>
    </body>
    </html>
    

    元のデータを元に戻す
    今、我々はQRコードのデータを解析して、すべてのチャンクを受け取った後にデータを復元しようとしなければなりません.
    var total = 0;
    var code_results = {};
    function processRead(result){
        var text = result["barcodeText"];
        try {
            var meta = text.split("|")[0];
            var totalOfThisOne = parseInt(meta.split("/")[1]);
            if (total!=0){
                if (total != totalOfThisOne){ //QR codes for another file
                    total = totalOfThisOne;
                    code_results={};
                    return;
                }
            }
    
            total = totalOfThisOne;
            var index = parseInt(meta.split("/")[0]);
            code_results[index]=result;
            if (getObjectLength(code_results)==total){
                onCompleted();
            }
    
        } catch(error) {
            console.log(error);
        }
    }
    function onCompleted(){
        showResult(timeElapsed);
    }
    
    async function showResult(timeElapsed){
        //combine chunks of bytes
        var jointData = [];
        for (var i=0;i<getObjectLength(code_results);i++){
            var index = i+1;
            var result = code_results[index];
            var bytes = result.barcodeBytes;
            var text = result.barcodeText;
            if (index == 1){
                var filename = text.split("|")[1]; //the first one contains progress|filename|image/webp|data
                var mimeType = text.split("|")[2];
                var firstSeparatorIndex = text.indexOf("|");
                var secondSeparatorIndex = text.indexOf("|",firstSeparatorIndex+1);
                var dataStart = text.indexOf("|",secondSeparatorIndex+1)+1;
                data = bytes.slice(dataStart,bytes.length);
            }else{
                var dataStart = text.indexOf("|")+1;
                data = bytes.slice(dataStart,bytes.length);
            }
            jointData = jointData.concat(data);
        }
        //display results on the page
        //...
        resetResults();
    }
    
    function resetResults(){
        code_results={};
        total = 0;
    }
    
    デコード結果は、Webページ上のリストに表示されます.
    つのリストの項目は、ファイルをダウンロードし、経過時間とダウンロード速度をダウンロードするリンクが含まれています.ファイルがイメージの場合、img要素も同様に追加されます.
    HTML
    <div id="decodedResults">
        Results:
        <ol id="decodedList">
        </ol>
    </div>
    
    ジャバスクリプト
    async function showResult(timeElapsed){
        //......
        var decodedList = document.getElementById("decodedList");
        var item = document.createElement("li");
        decodedList.appendChild(item);
        var dataURL = await ArraybufferAsDataURL(jointData,mimeType);
        appendDownloadLink(item, dataURL,filename);
        var info = document.createElement("span");
        info.innerText = " elapsed time: " + timeElapsed + "ms" +" speed: "+ (jointData.length/1024/(timeElapsed/1000)).toFixed(2) +"KB/s";
        item.appendChild(info);
        if (dataURL.indexOf("image")!=-1) {
            var img = document.createElement("img");
            img.src = dataURL;
            img.style.display = "block";
            img.style.maxHeight = "350px";
            img.style.maxWidth = "100%";
            item.appendChild(img);
        }
    }
    
    //https://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string
    const ArraybufferAsDataURL = async (data,mimeType) => {
        // Use a FileReader to generate a base64 data URI
        const dataUrl = await new Promise((r) => {
            const reader = new FileReader()
            reader.onload = () => r(reader.result)
            var array = ConvertToUInt8Array(data);
            var blob = new Blob([array],{type: mimeType});
            reader.readAsDataURL(blob)
        })
        return dataUrl;
    }
    
    function ConvertToUInt8Array(data){
        var array = new Uint8Array(data.length);
        for (var i=0;i<data.length;i++){
            array[i] = data[i];
        }
        return array;
    }
    
    function appendDownloadLink(item, dataURL,filename){
        var link = document.createElement('a');
        link.setAttribute('target', '_blank');
        link.setAttribute('href', dataURL);
        if (!filename){
            filename = "file";
        }
        link.setAttribute('download', filename);
        link.innerText=filename;
        item.appendChild(link);
    }
    
    function getObjectLength(obj){
        return getObjectKeys(obj).length;
    }
    
    function getObjectKeys(obj){
        return Object.keys(obj);
    }
    
    デコード処理中に、時間の経過と同様に統計情報が表示されます、フレーム処理、正常にフレームと進捗状況をお読みください.
    function updateStatistics(timeElapsed){
        var statisticsPre = document.getElementById("statisticsPre");
        statistics = "elapsed time: " + (timeElapsed)/1000 +"s";
        statistics = statistics +"\ntotal frame number: " + framesRead;
        statistics = statistics +"\nsuccessful number: " + successNum;
        statistics = statistics +"\nsuccess fps: " + (successNum/(timeElapsed/1000)).toFixed(2);
        statistics = statistics +"\nprogress: " + getObjectLength(code_results) + "/" + total;
        statisticsPre.innerHTML=statistics;
    }
    

    UIを定義する
    スキャナのUIはカスタマイズ可能です.
    スキャナ要素を作成し、要素を次のコードでバインドします.
    await scanner.setUIElement(document.getElementById('scanner'));
    
    定義済みクラスはいくつかあります.dbrScanner-video , dbrScanner-cvs-drawarea , dbrScanner-cvs-scanarea , dbrScanner-scanlight , dbrScanner-sel-camera , dbrScanner-sel-resolution . 彼らが存在するならば、DBR JSは彼らを見つけて、利用します.
    スキャナ要素:
    <div id="scanner" style="display:none;">
        <video class="dbrScanner-video" playsinline="true" style="width:100%;height:100%;position:absolute;left:0;top:0;"></video>
        <canvas class="dbrScanner-cvs-drawarea" style="width:100%;height:100%;position:absolute;left:0;top:0;"></canvas>
        <div class="dbrScanner-cvs-scanarea" style="width:100%;height:100%;position:absolute;left:0;top:0;">
            <div class="dbrScanner-scanlight" style="width:100%;height:3%;position:absolute;animation:3s infinite dbrScanner-scanlight;border-radius:50%;box-shadow:0px 0px 2vw 1px #00e5ff;background:#fff;display:none;"></div>
        </div>
        <select class="dbrScanner-sel-camera" style="margin:0 auto;position:absolute;left:0;top:0;height:30px;"></select>
        <select class="dbrScanner-sel-resolution" style="position:absolute;left:0;top:30px;"></select>
    </div>
    
    バーコードオーバーレイのキャンバスのようないくつかの要素を表示したくない場合は、単にそれを削除することができます.

    読書速度向上
    このスクリーン・カメラ・ソリューションの転送速度は、多くのQRコード・イメージが受信機を捕えて、固定期間でありえる方法によって、主に決定される.
    モバイルデバイスは毎秒30フレームをキャプチャすることができますが、1フレームをデコードするために何百ミリ秒かかることがあります.デコード性能は速度に影響を与える.
    これを改善する方法はいくつかあります.Dynamsoftバーコードリーダーは、特定の使用シナリオのパフォーマンスを最適化するために豊富なパラメータを提供します.

    QRコードのみスキャン
    Dynamsoftバーコードリーダーは、バーコード形式の多数をサポートしています.我々はそれだけで他のバーコード形式を見つける余分な努力を費やすことはありませんQRコードをスキャンする設定を更新することができます.
    let settings = await scanner.getRuntimeSettings();
    settings.barcodeFormatIds = Dynamsoft.DBR.EnumBarcodeFormat.BF_QR_CODE;
    await scanner.updateRuntimeSettings(settings);
    

    スキャン領域を設定する
    QRコードは、QRコードがフレームの大部分を占めるように、スキャン領域を設定することができるビデオフレーム全体の一部です.
    let settings = await scanner.getRuntimeSettings();
    settings.region.regionMeasuredByPercentage = 1; //use percentage
    var video = document.getElementsByTagName("video")[0];
    if (video.videoHeight>video.videoWidth){
        settings.region.regionLeft = 0;
        settings.region.regionTop = 25;
        settings.region.regionRight = 100;
        settings.region.regionBottom = 75;
    }else{
        settings.region.regionLeft = 25;
        settings.region.regionTop = 0;
        settings.region.regionRight = 75;
        settings.region.regionBottom = 100;
    }
    await scanner.updateRuntimeSettings(settings);
    


    より高速なローカライズ方法を使用する
    Dynamsoftバーコードリーダーは、いくつかのバーコードのローカライズ方法が付属しています.The scan directly モードは、電話でQRコードを読むのに適しています.
    let settings = await scanner.getRuntimeSettings();
    settings.localizationModes = [Dynamsoft.DBR.EnumLocalizationMode.LM_SCAN_DIRECTLY, 0, 0, 0, 0, 0, 0, 0, 0];
    await scanner.updateRuntimeSettings(settings);
    

    デモ
    IOSで実行している最終結果のデモです.


    試験と結論
    テストは、それがIOSデバイスとAndroidデバイス上でさまざまなチャンクのサイズと間隔で実行する方法を検討するアニメーションQRコードジェネレータとスキャナのこの組み合わせのために実行されます.つの連続測定値の速度の結果が記録されます(大きなファイル、1つだけのレコード).
    iPhone SE 2016のテスト結果はこちら
  • ファイルサイズ:15.93 KB、チャンクサイズ:1800、間隔:100
  • ファイルサイズ:15.93 KB、チャンクサイズ:1800、間隔:200、速度:7.22 kB/s、7.17 kb/s、7.47 kbs/s
  • ファイルサイズ:15.93 KB、チャンクサイズ:1800、間隔:400
  • ファイルサイズ:15.93 KB、チャンクサイズ:1800、間隔:800
  • ファイルサイズ:15.93 KB、チャンクサイズ:2900、間隔:100
  • ファイルサイズ:15.93 KB、チャンクサイズ:2900、間隔:200
  • ファイルサイズ:15.93 KB、チャンクサイズ:2900、間隔:400
  • ファイルサイズ:15.93 KB、チャンクサイズ:2900、間隔:800
  • ファイルサイズ:231 KB、チャンクサイズ:1800、間隔:400
  • ファイルサイズ:231 KB、チャンクサイズ:2900、間隔:400、速度:2.12 KB/s
  • ここではシャープなAquos S 2のテスト結果です.
  • ファイルサイズ:15.93 KB、チャンクサイズ:1800、間隔:100
  • ファイルサイズ:15.93 KB、チャンクサイズ:1800、間隔:200、速度:2.18 kB/s、1.38 kb/s、1.91 kb/s
  • ファイルサイズ:15.93 KB、チャンクサイズ:1800、間隔:400
  • ファイルサイズ:15.93 KB、チャンクサイズ:1800、間隔:800、速度:2.03
  • ファイルサイズ:15.93 KB、チャンクサイズ:2900、間隔:100
  • ファイルサイズ:15.93 KB、チャンクサイズ:2900、間隔:200
  • ファイルサイズ:15.93 KB、チャンクサイズ:2900、間隔:400
  • ファイルサイズ:15.93 KB、チャンクサイズ:2900、間隔:800
  • ファイルサイズ:229 KB、チャンクサイズ:1800、間隔:400
  • ファイルサイズ:229 KB、チャンクサイズ:2900、間隔:400、速度:1.05 kB/s
  • 我々は、このテストに基づいて発見的に結論を出すことができます.
  • 200 KBの下にある小さなファイルを転送するためのソリューションは素晴らしい作品.速度が制限されるので、より多くのQRコードが大きいファイルをコード化するのに必要であるので、大きいファイルのためにフレームを欠落させる機会は高いです.
  • したがって、チャンクのサイズと間隔を調整する必要があります.チャンクサイズと間隔が小さい場合、ジェネレータは高速でQRコードを生成することができます、しかし、受信機は時間内にそれらを捕えないかもしれません.チャンクサイズが大きいなら、同じ時間範囲でより多くのデータを転送することができます、しかし、レシーバーはより多くの時間を解読して、フレームを欠くかもしれません、特にローエンドの装置のために.
  • この解決策は次のように改良することができる.
  • より大きなデータ容量を持つことができる色のQRコードのような新しい形式を使用して速度を向上させる.
  • 改善Funtainコードを使用してフレームの問題を逃した.
  • ジェネレータが非デコードされたQRコードを示すだけであるように、双方向通信に現在の一方向通信をすることによって、逃したフレーム問題を改善してください.これは、受信機の画面に表示されるQRコードをスキャンするためにジェネレータを使用することによって行うことができます.
  • データ圧縮を使用します.JPEGファイルを転送する代わりに、WebPファイルを転送します.( conversion tool )

  • ソースコード
    https://github.com/xulihang/AnimatedQRCodeReader