Does WebGL Speed Up JavaScript Barcode Decoding? GPU Grayscale vs Canvas
WebGL can convert a camera frame to grayscale on the GPU before Dynamsoft Barcode Reader decodes it, but that does not automatically raise scan FPS. CaptureVisionRouter.capture() accepts a grayscale buffer (IPF_GRAYSCALED, one byte per pixel) or a canvas color buffer (IPF_ABGR_8888, four bytes per pixel). In this 2026 PC sample with dynamsoft-barcode-reader-bundle 11.6.2100, grayscale decode was only about 7–12% faster, while gl.readPixels() was about 4–5× slower than canvas getImageData(), so the CPU color path had the lower total time. Phones were not measured here; tile-based mobile GPUs usually make readPixels stalls worse, not better. Use the sample to copy the v10+/v11 buffer APIs, then measure on the device you ship.
This article is Part 1 in a 1-Part Series.
Key Takeaways
- Grayscale input (
IPF_GRAYSCALED) can make decode slightly faster because the SDK receives one channel instead of four. - WebGL conversion only helps if shader time plus
gl.readPixels()is cheaper than canvasgetImageData()plus the extra color-decode cost. That was not true on this PC. - In this 2026 PC sample, CPU color had the lower total time at 640×480 (
20.57 msvs24.41 ms) and 1280×720 (28.61 msvs36.05 ms). - Do not assume a phone reverses the result. Mobile GPUs are tile-based;
readPixelsforces a full-frame GPU-to-CPU flush that is often slower than on desktop. - Pass canvas pixels as a
Uint8Array(IPF_ABGR_8888), notImageData.data(Uint8ClampedArray), orcapture()rejects the buffer.
Online Demo
https://yushulx.me/javascript-barcode-qr-code-scanner/examples/webgl/
License Activation
Obtain a trial license to activate the Dynamsoft JavaScript Barcode SDK:
Dynamsoft.License.LicenseManager.initLicense("LICENSE-KEY");
Installation
The Dynamsoft JavaScript Barcode SDK is available on npmjs.com:
https://www.npmjs.com/package/dynamsoft-barcode-reader-bundle
You can also load it directly from a CDN:
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-barcode-reader-bundle@11.6.2100/dist/dbr.bundle.js"></script>
Decoding Barcodes and QR Codes from HTML5 Canvas
The following code demonstrates how to render a video stream to an HTML canvas and invoke the capture() method of the CaptureVisionRouter class to decode barcodes or QR codes from the canvas. In the current SDK (v10+), CaptureVisionRouter replaces the old BarcodeReader class, and the raw pixel buffer is passed as a DSImageData-compatible object (bytes, width, height, stride, format). Canvas getImageData() returns R,G,B,A bytes in memory, which the new SDK expresses as IPF_ABGR_8888.
var barcodereader = null;
(async()=>{
// v10+ API: create a CaptureVisionRouter instance
barcodereader = await Dynamsoft.CVR.CaptureVisionRouter.createInstance();
// Use the preset template that prioritizes decoding speed
let settings = await barcodereader.getSimplifiedSettings('ReadBarcodes_SpeedFirst');
settings.barcodeSettings.deblurLevel = 0;
await barcodereader.updateSettings('ReadBarcodes_SpeedFirst', settings);
})();
let canvas2d = document.createElement('canvas');
canvas2d.width = width;
canvas2d.height = height;
var ctx2d = canvas2d.getContext('2d');
ctx2d.drawImage(videoElement, 0, 0, width, height);
var imgData = ctx2d.getImageData(0, 0, width, height);
// capture() rejects Uint8ClampedArray; copy into a Uint8Array.
buffer = new Uint8Array(imgData.data);
if (barcodereader){
barcodereader
.capture(
{
bytes: buffer,
width: width,
height: height,
stride: width * 4,
format: Dynamsoft.Core.EnumImagePixelFormat.IPF_ABGR_8888
},
'ReadBarcodes_SpeedFirst'
)
.then((result) => {
showResults(result);
});
}
Converting a Color Image to a Grayscale Image with WebGL
Theoretically, processing grayscale images is faster than processing color images because a color image has four channels, whereas a grayscale image has only one. If the input data source is a grayscale image, the above code can be modified accordingly:
barcodereader
.capture(
{
bytes: gray,
width: width,
height: height,
stride: width,
format: Dynamsoft.Core.EnumImagePixelFormat.IPF_GRAYSCALED
},
'ReadBarcodes_SpeedFirst'
)
.then((result) => {
showResults(result);
});
The next question is: How can we use WebGL to convert a color image to a grayscale image? A website called WebGLFundamentals offers a crash course on WebGL.
By learning WebGL, we use the following steps for image pre-processing:
-
Create a shader program for color conversion:
<!-- https://gist.github.com/Volcanoscar/4a9500d240497d3c0228f663593d167a --> <script id="drawImage-fragment-shader" type="x-shader/x-fragment"> precision mediump float; varying vec2 v_texcoord; uniform sampler2D u_texture; uniform float u_colorFactor; void main() { vec4 sample = texture2D(u_texture, v_texcoord); float grey = 0.21 * sample.r + 0.71 * sample.g + 0.07 * sample.b; gl_FragColor = vec4(sample.r * u_colorFactor + grey * (1.0 - u_colorFactor), sample.g * u_colorFactor + grey * (1.0 - u_colorFactor), sample.b * u_colorFactor + grey * (1.0 - u_colorFactor), 1.0); } </script> -
Call the
draw()function to bind the video element to a WebGL texture:var drawInfo = { x: 0, y: 0, dx: 1, dy: 1, textureInfo: loadImageAndCreateTextureInfo(videoElement) }; draw(drawInfo); -
Read the image data into a Uint8Array:
buffer = new Uint8Array(width * height * 4); gl.readPixels( 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RGBA, gl.UNSIGNED_BYTE, buffer );An interesting point is that the output image is upside down. To flip the image data along its vertical axis, you can use gl.pixelStorei():
function drawImage(tex, texWidth, texHeight, dstX, dstY) { gl.bindTexture(gl.TEXTURE_2D, tex); gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); -
Extract the grayscale data from the buffer:
gray = new Uint8Array(width * height); let gray_index = 0; for (i = 0; i < width * height * 4; i += 4) { gray[gray_index++] = buffer[i]; }
Here is the screenshot of the running complete code:

Processing the Captured Result
In the current SDK, capture() returns a CapturedResult object instead of the array returned by the old decodeBuffer(). Filter the barcode items with the CRIT_BARCODE type, read the text with item.text, and draw the bounding box with item.location.points:
function showResults(result) {
let context = clearOverlay();
let txts = [];
let items = result.items || [];
let barcodeItems = items.filter((item) => item.type === Dynamsoft.Core.EnumCapturedResultItemType.CRIT_BARCODE);
if (barcodeItems.length > 0) {
for (var i = 0; i < barcodeItems.length; ++i) {
txts.push(barcodeItems[i].text);
let points = barcodeItems[i].location.points;
context.beginPath();
context.moveTo(points[0].x, points[0].y);
context.lineTo(points[1].x, points[1].y);
context.lineTo(points[2].x, points[2].y);
context.lineTo(points[3].x, points[3].y);
context.closePath();
context.stroke();
context.fillText(barcodeItems[i].text, points[0].x, points[0].y + 50);
}
barcode_result.textContent = txts.join(', ');
}
else {
barcode_result.textContent = "No barcode found";
}
}
Barcode Decoding Performance Comparison: Grayscale Image vs Color Image
To measure the performance difference, use the formula:
total time = image data obtaining time + barcode decoding time
The original 640×480 camera-stream test on the author’s PC showed WebGL readPixels about 5× slower than canvas getImageData(), while grayscale decode used less CPU than color decode:

A 2026 re-test of the same sample (Dynamsoft Barcode Reader bundle 11.6.2100, ReadBarcodes_SpeedFirst, still images with 3 QR codes, 15 rounds after 3 warmups) measured:
| Frame | Path | Buffer avg | Decode avg | Total avg |
|---|---|---|---|---|
| 640×480 | GPU grayscale (readPixels + IPF_GRAYSCALED) |
6.40 ms | 18.01 ms | 24.41 ms |
| 640×480 | CPU color (getImageData + IPF_ABGR_8888) |
1.27 ms | 19.30 ms | 20.57 ms |
| 1280×720 | GPU grayscale | 13.68 ms | 22.37 ms | 36.05 ms |
| 1280×720 | CPU color | 3.50 ms | 25.11 ms | 28.61 ms |
In this PC benchmark, grayscale decode was about 7% faster at 640×480 and about 12% faster at 1280×720. WebGL buffer grab stayed about 4×–5× slower than canvas (6.40 ms vs 1.27 ms at 640×480; 13.68 ms vs 3.50 ms at 1280×720), so the CPU color path had the lower total time at both sizes.
That gap is about transfer, not about “GPU cannot convert color to gray.” A 640×480 grayscale shader is cheap; copying the result back with gl.readPixels() is not. On a desktop GPU the flush already cost more than the decode savings. Phones were not re-tested in 2026. Mobile GPUs are typically tile-based (PowerVR, Mali, Adreno, Apple GPU): readPixels resolves the whole frame from tile memory into CPU memory and often stalls the next frame. Safari and Chrome on iOS/Android are therefore unlikely to make this WebGL path faster than canvas; if anything, the readPixels tax is usually worse than on PC. Treat GPU grayscale as an optional experiment, not the default scanner pipeline, until you measure it on the device you ship.
Common Developer Questions
How do I migrate from the 9.x BarcodeReader API to the new SDK?
Replace Dynamsoft.DBR.BarcodeReader.license = "..." with Dynamsoft.License.LicenseManager.initLicense("..."), create the instance with Dynamsoft.CVR.CaptureVisionRouter.createInstance(), replace decodeBuffer() with capture(), and replace the pixel format enum IPF_GrayScaled with IPF_GRAYSCALED.
Which npm package should I install for barcode decoding in the browser?
Install the dynamsoft-barcode-reader-bundle package. Load dist/dbr.bundle.js from the CDN or install it via npm, then use the Dynamsoft.CVR.CaptureVisionRouter API shown in this article.
Can I still pass a raw Uint8Array pixel buffer to the SDK?
Yes. Pass a DSImageData-compatible object to capture(): { bytes, width, height, stride, format }. For a WebGL grayscale buffer, use stride: width and Dynamsoft.Core.EnumImagePixelFormat.IPF_GRAYSCALED. Canvas ImageData.data is a Uint8ClampedArray; copy it with new Uint8Array(imgData.data) before calling capture(), or the SDK rejects the buffer.
Which pixel format should I use for canvas getImageData() bytes?
Canvas getImageData() returns R,G,B,A bytes in memory order, which the new SDK interprets as IPF_ABGR_8888 (the old 9.x SDK called the same layout IPF_ARGB_8888). Using the wrong enum swaps the red and blue channels, which can hurt the read rate on color barcodes.
Which preset template should I use for fast decoding from a video stream?
The ReadBarcodes_SpeedFirst preset template prioritizes decoding speed. It replaces the old updateRuntimeSettings('speed') + deblurLevel = 0 pattern in the 9.x SDK.
Does WebGL actually make barcode decoding faster?
Not in this PC sample. Grayscale decode was only about 7–12% faster than color decode, and gl.readPixels() cost about 4×–5× more than canvas getImageData() (6.40 ms vs 1.27 ms at 640×480; 13.68 ms vs 3.50 ms at 1280×720), so CPU color had the lower total time.
Will a phone reverse the PC result and make WebGL faster?
Do not assume that. This 2026 re-test is PC-only. Mobile GPUs are tile-based, and readPixels forces a full-frame GPU-to-CPU flush that is often slower than on desktop. Measure readPixels versus getImageData() on the target phone before using WebGL as the default path.