Using Web Worker to Load WebAssembly Barcode SDK

Web Worker is a way to run JavaScript in background threads. The worker thread can perform some heavy works without interfering with the user interface. This article shares how to load WebAssembly and invoke Dynamsoft JavaScript barcode APIs in a web worker.

Web Worker for WASM

If you have tried the ‘hello world’ example of Dynamsoft JavaScript Barcode SDK, you should know that the SDK is built based on WebAssembly technology, which allows you to develop web client-side barcode reader app in pure JavaScript. The simple example demonstrates how to decode barcode from an input file.

However, in most scenarios, a web barcode reader features real-time decoding for camera video stream. The principle for building a camera app is don’t block the UI thread. If you call the barcode API in JavaScript main thread and the API takes more than 100ms to decode barcodes, the UI cannot render smoothly. That is why we need to use web workers.

When using WebAssembly, it may take a long time to download and compile the wasm file. Dynamsoft provides two JavaScript barcode editions built with different optimization levels which influence the final wasm file size and initialization time. In worker.js, add the following code:

function browserRedirect() {
    var deviceType;
    var sUserAgent = navigator.userAgent.toLowerCase();
    var bIsIpad = sUserAgent.match(/ipad/i) == "ipad";
    var bIsIphoneOs = sUserAgent.match(/iphone os/i) == "iphone os";
    var bIsMidp = sUserAgent.match(/midp/i) == "midp";
    var bIsUc7 = sUserAgent.match(/rv:1.2.3.4/i) == "rv:1.2.3.4";
    var bIsUc = sUserAgent.match(/ucweb/i) == "ucweb";
    var bIsAndroid = sUserAgent.match(/android/i) == "android";
    var bIsCE = sUserAgent.match(/windows ce/i) == "windows ce";
    var bIsWM = sUserAgent.match(/windows mobile/i) == "windows mobile";
    if (bIsIpad || bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM) {
      deviceType = 'phone';
    } else {
      deviceType = 'pc';
    }
    return deviceType;
}

if (browserRedirect() === 'pc') {
    importScripts('https://demo.dynamsoft.com/dbr_wasm/js/dbr-6.3.0.1.min.js');
}
else {
    importScripts('https://demo.dynamsoft.com/dbr_wasm/js/dbr-6.3.0.1.mobile.min.js');
}

To load the WebAssembly in iOS Safari with less time, use  https://demo.dynamsoft.com/dbr_wasm/js/dbr-6.3.0.1.mobile.min.js.

Initialize the barcode reader:

var reader;
var dynamsoft = self.dynamsoft || {};
dynamsoft.dbrEnv = dynamsoft.dbrEnv || {};
dynamsoft.dbrEnv.resourcesPath = 'https://demo.dynamsoft.com/dbr_wasm/js/';

dynamsoft.dbrEnv.onAutoLoadWasmSuccess = function () {
    reader = new dynamsoft.BarcodeReader();
    postMessage({
        event: "onload",
        body: "Successfully loaded wasm."
    });
};
dynamsoft.dbrEnv.onAutoLoadWasmError = function (status) {
    postMessage({
        event: "onerror",
        body: "Failed to load wasm."
    });
};
//https://www.dynamsoft.com/CustomerPortal/Portal/TrialLicense.aspx
dynamsoft.dbrEnv.licenseKey = "t0068MgAAAD2IrA1WJjiVx78RfaZ46qMyCY8DaqpvAD57z5QWkwVQkVwZEf7lE+M2QYbnPx9Fu/aFvCL1mz0Kh2YK0milUng=";

Wait for barcode decoding event:

onmessage = function (e) {
    e = e.data;
    switch (e.type) {
        case "decodeBuffer":
            {
                self.reader.decodeBuffer(e.body, e.width, e.height, e.width * 4, dynamsoft.BarcodeReader.EnumImagePixelFormat.IPF_ARGB_8888).then(results => {
                    postMessage({
                        event: 'onresult',
                        body: results
                    });
                }).catch(ex => {
                    postMessage({
                        event: 'onresult',
                        body: 'No barcode detected'
                    });
                });
                break;
            }
        default:
            break;
    }
};

In the main thread, create the web worker:

if (window.Worker) {
  myWorker = new Worker('worker.js');
  myWorker.onmessage = function (e) {
    let data = e.data;
    if (data.event) {
      switch (data.event) {
        case 'onload':
          {
            document.getElementById('anim-loading').style.display = 'none';
            buttonFile.disabled = false;
            buttonVideo.disabled = false;
            break;
          }
        case 'onerror':
          {
            document.getElementById('anim-loading').style.display = 'none';
            break
          }
        case 'onresult':
          {
            let context = clearOverlay();

            let txts = [];
            try {
              let results = data.body;
              let localization;
              for (var i = 0; i < results.length; ++i) {
                if (results[i].LocalizationResult.ExtendedResultArray[0].Confidence >= 30) {
                  txts.push(results[i].BarcodeText);
                  localization = results[i].LocalizationResult
                  if (isVideoMode) {
                    drawResult(context, localization, results[i].BarcodeText);
                  }
                }
              }
              barcode_result.textContent = txts.join(', ');

              if (isVideoMode) {
                scanBarcode();
              }
            } catch (e) {
              if (isVideoMode) {
                scanBarcode();
              } else {
                barcode_result.textContent = data.body;
              }
            }

            break;
          }
      }
    }
  };
}

Capture a frame from the video and send it to the web worker:

function scanBarcode() {

  let context = ctx,
    width = videoWidth,
    height = videoHeight;

  context.drawImage(videoElement, 0, 0, width, height);
  var barcodeCanvas = document.createElement("canvas");
  barcodeCanvas.width = width;
  barcodeCanvas.height = height;
  var barcodeContext = barcodeCanvas.getContext('2d');
  barcodeContext.drawImage(videoElement, 0, 0, width, height);
  // read barcode
  if (myWorker) {
    myWorker.postMessage({
      type: 'decodeBuffer',
      body: barcodeContext.getImageData(0, 0, width, height).data,
      width: width,
      height: height
    });
  }
}

Draw returned results on overlay:

function drawResult(context, localization, text) {
  context.beginPath();
  context.moveTo(localization.X1, localization.Y1);
  context.lineTo(localization.X2, localization.Y2);
  context.lineTo(localization.X3, localization.Y3);
  context.lineTo(localization.X4, localization.Y4);
  context.lineTo(localization.X1, localization.Y1);
  context.stroke();

  context.font = "18px Verdana";
  context.fillStyle = '#ff0000';
  let x = [localization.X1, localization.X2, localization.X3, localization.X4];
  let y = [localization.Y1, localization.Y2, localization.Y3, localization.Y4];
  x.sort(function (a, b) {
    return a - b
  });
  y.sort(function (a, b) {
    return b - a
  });
  let left = x[0];
  let top = y[0];

  context.fillText(text, left, top + 50);
}

Deploy the project and then visit the app page:

web worker wasm

wasm web worker

Source Code

https://github.com/dynamsoft-dbr/javascript-barcode/tree/master/examples/webworker