Added scanning barcodes with a camera

This commit is contained in:
2026-03-08 16:59:33 +00:00
parent b4f8489834
commit 5a37e5dd5f
404 changed files with 224181 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
title: Quagga2 Documentation
description: Barcode scanning for browser and node!
theme: jekyll-theme-cayman

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

View File

@@ -0,0 +1,119 @@
## Contributing {#contributing}
### Questions, Bugs, Enhancements / Suggestions {#questions-bugs-enhancements}
For questions, bug reports, and enhancement requests / suggestions, please use the GitHub issue
tracker at https://github.com/ericblade/quagga2/issues
### Chat / Real Time Communication? {#chat}
We have a Gitter chat channel available at https://gitter.im/quaggaJS/Lobby . Please join us there,
and be patient. Thanks!
### Code {#code}
#### Developing enhancements to Quagga2 {#developing-enhancements}
If you'd like to work directly on the Quagga2 code, it is quite easy to build a local development
copy and get started hacking on the base code. Simply clone this repository, then run:
```
npm install
npm run build
```
#### Running tests {#running-tests}
There are several tests included in Quagga2, in the test/ folder. Please make sure before you
send in any pull requests, that you have run ```npm test``` against the existing tests, as well as
implemented any tests that may be needed to test your new code. If you're not sure how to properly
unit test your new code, then go ahead and make that pull request, and we'll try to help you before
merging.
**Test Structure**:
- Unit tests are in `src/` alongside source files (`.spec.ts` files)
- Integration tests are in `test/integration/` organized by decoder type
- See `test/integration/README.md` for detailed information about integration test structure and configuration
**Running Specific Tests**:
```bash
# All tests (Node unit/integration + full Cypress suite)
npm test
# Run Node and Cypress in parallel (faster local iteration)
npm run test:parallel
# Node tests only
npm run test:node
# Browser tests only (requires Cypress)
npm run test:browser-all
# E2E examples spec with server (headless)
npm run test:e2e
# E2E examples spec (interactive, Electron)
npm run test:e2e:open
# Cypress: run only specific spec(s)
# Pass --spec through npm using --
npm run cypress:run -- --spec cypress/e2e/browser.cy.ts
# Multiple specs (comma-separated)
npm run cypress:run -- --spec cypress/e2e/browser.cy.ts,cypress/e2e/integration.cy.ts
# Glob pattern
npm run cypress:run -- --spec "cypress/e2e/**/browser*.cy.ts"
# Open interactive runner
npm run cypress:open
# Open only the examples E2E spec (Electron)
npm run cypress:open:e2e
# Choose a browser (if installed)
npx cross-env NODE_ENV=development BUILD_ENV=development NODE_OPTIONS=--openssl-legacy-provider cypress run --browser chrome --env BUILD_ENV=development --spec cypress/e2e/browser.cy.ts
# Integration tests only (Node)
npx ts-mocha -p test/tsconfig.json test/integration/**/*.spec.ts
# Specific decoder integration tests
npx ts-mocha -p test/tsconfig.json test/integration/decoders/ean_8.spec.ts
```
**Adding Integration Tests**:
When adding new test cases to decoder integration tests, tests should pass in both Node and browser
environments by default. If a test is known to fail in a specific environment, mark it explicitly with:
- `allowFailInNode: true` - Test can fail in Node without failing CI
- `allowFailInBrowser: true` - Test can fail in browser without failing CI
- Both flags - Test can fail in both environments
See `test/integration/README.md` for complete details on the test failure marking system.
**Notes**:
- The Cypress E2E examples (`cypress/e2e/examples.cy.ts`) require the local examples server on port 8080; the `test`, `test:e2e`, and `test:e2e:open` scripts start it automatically via `start-server-and-test`.
- The parallel runner uses prefixed logs (`e2e`, `node`) and exits non-zero if either side fails (`--kill-others-on-fail --success=all`).
#### Working on a changed copy of Quagga2 from another repository (ie, developing an external plugin) {#working-on-external-plugin}
If you need to make changes to Quagga2 to support some external code (such as an external reader plugin),
you will probably need to be able to test the code in your other repo. One such way to do that is
to ```npm install @ericblade/quagga2``` inside the external repo, which will initialize the module
structure, and fill it with the current release of quagga2. Once that is completed, then copy the
lib/quagga.js (node) and/or dist/quagga.js and/or dist/quagga.min.js files into
ExternalRepo/node_modules/@ericblade/quagga2, preserving the "lib" or "dist" folder. Then your code
will have the new changes that you have implemented in your copy of the quagga2 repo.
#### Pull requests {#pull-requests}
Please, submit pull requests! Open source works best when we all work together. If you find a change
to be useful, the odds are that other users will, as well!
## BE AWESOME {#be-awesome}
Don't forget to BE AWESOME.

View File

@@ -0,0 +1,7 @@
{
"parserOptions": {
"ecmaFeatures": {
"jsx": true
}
}
}

View File

@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<title>Camera</title>
<script type="text/javascript">
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
function getUserMedia(constraints, success, failure) {
navigator.getUserMedia(constraints, function(stream) {
var videoSrc = (window.URL && window.URL.createObjectURL(stream)) || stream;
success.apply(null, [videoSrc]);
}, failure);
}
function initCamera(constraints, video, callback) {
getUserMedia(constraints, function (src) {
video.src = src;
video.addEventListener('loadeddata', function() {
var attempts = 10;
function checkVideo() {
if (attempts > 0) {
if (video.videoWidth > 0 && video.videoHeight > 0) {
console.log(video.videoWidth + "px x " + video.videoHeight + "px");
video.play();
callback();
} else {
window.setTimeout(checkVideo, 100);
}
} else {
callback('Unable to play video stream.');
}
attempts--;
}
checkVideo();
}, false);
}, function(e) {
console.log(e);
});
}
function copyToCanvas(video, ctx) {
( function frame() {
ctx.drawImage(video, 0, 0);
window.requestAnimationFrame(frame);
}());
}
window.addEventListener('load', function() {
var constraints = {
video: {
mandatory: {
minWidth: 1280,
minHeight: 720
}
}
},
video = document.createElement('video'),
canvas = document.createElement('canvas');
document.body.appendChild(video);
document.body.appendChild(canvas);
initCamera(constraints, video, function() {
canvas.setAttribute('width', video.videoWidth);
canvas.setAttribute('height', video.videoHeight);
copyToCanvas(video, canvas.getContext('2d'));
});
}, false);
</script>
</head>
<body>
<footer>
Quagga2 Project &copy; 2014-2025 Christoph Oberhofer, Eric Blade, and all other Contributors. See <a href="https://github.com/ericblade/quagga2/blob/master/package.json">package.json</a>. Licensed under MIT license. See <a href="https://github.com/ericblade/quagga2/blob/master/LICENSE">LICENSE</a>
</footer>
</body>
</html>

View File

@@ -0,0 +1,27 @@
# Require any additional compass plugins here.
# Set this to the root of your project when deployed:
http_path = "/"
css_dir = "css"
sass_dir = "sass"
images_dir = "img"
javascripts_dir = "js"
fonts_dir="fonts"
# You can select your preferred output style here (can be overridden via the command line):
# output_style = :expanded or :nested or :compact or :compressed
# To enable relative paths to assets via compass helper functions. Uncomment:
# relative_assets = true
# To disable debugging comments that display the original location of your selectors. Uncomment:
# line_comments = false
# If you prefer the indented syntax, you might want to regenerate this
# project again passing --syntax sass, or you can uncomment this:
# preferred_syntax = :sass
# and then run:
# sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass

View File

@@ -0,0 +1,16 @@
@charset "UTF-8";
/* LESS - http://lesscss.org style sheet */
/* Palette color codes */
/* Palette URL: http://paletton.com/#uid=31g0q0kHZAviRSkrHLOGomVNzac */
/* Feel free to copy&paste color codes to your application */
/* MIXINS */
/* As hex codes */
/* Main Primary color */
/* Main Secondary color (1) */
/* Main Secondary color (2) */
/* As RGBa codes */
/* Main Primary color */
/* Main Secondary color (1) */
/* Main Secondary color (2) */
/* Generated by Paletton.com ├é┬® 2002-2014 */
/* http://paletton.com */

View File

@@ -0,0 +1 @@
@import url("https://fonts.googleapis.com/css?family=Ubuntu:400,700|Cabin+Condensed:400,600");

View File

@@ -0,0 +1,298 @@
@charset "UTF-8";
@import url("https://fonts.googleapis.com/css?family=Ubuntu:400,700|Cabin+Condensed:400,600");
body {
background-color: #FFF;
margin: 0px;
font-family: Ubuntu, sans-serif;
color: #1e1e1e;
font-weight: normal;
padding-top: 0;
}
h1, h2, h3, h4 {
font-family: "Cabin Condensed", sans-serif;
}
header {
background: #FFC600;
padding: 1em;
}
header .headline {
max-width: 640px;
margin: 0 auto;
}
header .headline h1 {
color: #FFDD69;
font-size: 3em;
margin-bottom: 0;
}
header .headline h2 {
margin-top: 0.2em;
}
footer {
background: #0A4DB7;
color: #6C9CE8;
padding: 1em 2em 2em;
}
#container {
width: 640px;
margin: 20px auto;
padding: 10px;
}
#interactive.viewport {
width: 640px;
height: 480px;
}
#interactive.viewport canvas, video {
float: left;
width: 640px;
height: 480px;
}
#interactive.viewport canvas.drawingBuffer, video.drawingBuffer {
margin-left: -640px;
}
.controls fieldset {
border: none;
margin: 0;
padding: 0;
}
.controls .input-group {
float: left;
}
.controls .input-group input, .controls .input-group button {
display: block;
}
.controls .reader-config-group {
float: right;
}
.controls .reader-config-group label {
display: block;
}
.controls .reader-config-group label span {
width: 9rem;
display: inline-block;
text-align: right;
}
.controls:after {
content: '';
display: block;
clear: both;
}
#result_strip {
margin: 10px 0;
border-top: 1px solid #EEE;
border-bottom: 1px solid #EEE;
padding: 10px 0;
}
#result_strip > ul {
padding: 0;
margin: 0;
list-style-type: none;
width: auto;
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
}
#result_strip > ul > li {
display: inline-block;
vertical-align: middle;
width: 160px;
}
#result_strip > ul > li .thumbnail {
padding: 5px;
margin: 4px;
border: 1px dashed #CCC;
}
#result_strip > ul > li .thumbnail img {
max-width: 140px;
}
#result_strip > ul > li .thumbnail .caption {
white-space: normal;
}
#result_strip > ul > li .thumbnail .caption h4 {
text-align: center;
word-wrap: break-word;
height: 40px;
margin: 0px;
}
#result_strip > ul:after {
content: "";
display: table;
clear: both;
}
.scanner-overlay {
display: none;
width: 640px;
height: 510px;
position: absolute;
padding: 20px;
top: 50%;
margin-top: -275px;
left: 50%;
margin-left: -340px;
background-color: #FFF;
-moz-box-shadow: #333333 0px 4px 10px;
-webkit-box-shadow: #333333 0px 4px 10px;
box-shadow: #333333 0px 4px 10px;
}
.scanner-overlay > .header {
position: relative;
margin-bottom: 14px;
}
.scanner-overlay > .header h4, .scanner-overlay > .header .close {
line-height: 16px;
}
.scanner-overlay > .header h4 {
margin: 0px;
padding: 0px;
}
.scanner-overlay > .header .close {
position: absolute;
right: 0px;
top: 0px;
height: 16px;
width: 16px;
text-align: center;
font-weight: bold;
font-size: 14px;
cursor: pointer;
}
i.icon-24-scan {
width: 24px;
height: 24px;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzFFMjMzNTBFNjcwMTFFMkIzMERGOUMzMzEzM0E1QUMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzFFMjMzNTFFNjcwMTFFMkIzMERGOUMzMzEzM0E1QUMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDMUUyMzM0RUU2NzAxMUUyQjMwREY5QzMzMTMzQTVBQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDMUUyMzM0RkU2NzAxMUUyQjMwREY5QzMzMTMzQTVBQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtQr90wAAAUuSURBVHjanFVLbFRVGP7ua97T9DGPthbamAYYBNSMVbBpjCliWWGIEBMWsnDJxkh8RDeEDW5MDGticMmGBWnSlRSCwgLFNkqmmrRIqzjTznTazkxn5s7c6/efzm0G0Jhwkj/nP+d/nv91tIWFBTQaDQWapkGW67p4ltUub5qmAi0UCqF/a/U2m81tpmddotwwDGSz2dzi4uKSaOucnJycGhsbe1XXdQiIIcdxEAgEtgXq9brySHCht79UXi/8QheawN27d385fPjwuEl6XyKR6LdtW7t06RLK5TKOHj2K/fv3Q87Dw8OYn5/HiRMnMDs7i5mZGQwODiqlPp8PuVwO6XRaOXb16lXl1OnTp5FMJvtosF8M+MWLarWqGJaWlpBKpRRcu3YN4+PjmJ6exsTEhDJw5coVjI6OKgPhcBiZTAbxeBx+vx+XL19Gd3c3Tp48Ka9zqDYgBlTQxYNgMIhIJKLCILkQb+TZsgvdsiyFi+feWRR7oRNZyanQtvW2V4DEUUBiK2eJpeDirSyhCe7F2QPh8fiEp72i9PbsC5G52DbiKZA771yr1dTuGfJ4PQNPFoAyQNR1aNEmsS5eyB3PgjeooMZd2AWvNmzYci/Gea7TeFOcI93jV/K67noGmi4vdRI9gPSDeMLSdKUBZZczlWm1rTtHjLZ24d+WER2tc8N1m+Y+ID74wx0zGYvhg9UNrJdtHJyZRdQfwPsrq9g99xsGlgsYmr6BNzO/IVwsYfjBQ6XYz6JI/72MV366B5/lw0elOkJWGUM3bmKtWjXSLuLaBWhnPnnp0FfoiFi4+TMfVAb2poBkDLjO845uYLEAjL4ALGWBP5YAOsP4AJYBFDaB1HOSVWD2PuV95H2RdV93Lv74/cf6p6Zxq/h6OofeOPJBC39JtONdwOAAViOs4p4OFGTf0Uc8iiyrr9YdQrUnDLsngrVOC0jQib44HlF2RafRZBz1Qy+vfhgK3NJZBlrm+LEm9qWwzFgLU7Ozg0JxZP06jQSRpQ7EerAWDSt6PuhHPmChEAog56fCLvJT5hHTm3OZkz3DyLx7XNWTGEA1GkV14gjWgwbW0ESVjYRwCOuai03L5E7OUBAV4kXSS4auoGIaKOma4m8EA5R1sMEGLh95C+XuLph0WJWpxepYYLtfT0RRgY1KgNODY6BoaChRuEhDCIZQYseuki5KN6hcQHiq7OZNv4/Zq2O6P4Lfkwn46vZjjaYZrIpvWbpzjLErrc4xUGE4avRedpYJalRcIl5hQius/SrPm9xrNOQYJhao6BvNUeWqtY8KaWuNjHOFAr7mM9f4NA4UbKysoUJ8PV9UzVOx6wxDDWUOxnK1pmCD07fOMAvtIsM3l89Dl3HRGhVma9AZMqjOnz2LQqWCxs6dqr3T7x1DTzKJaG8SekcHhg4cgI/56uKdlKnBV/WndqN3YAB/7tyBd3oT6GBIOzs7kc/nDfFdDFT5bS73cp06dQoaPa/Rw/rtO/resTHxxE2m9rCrbSR27UJCcMf1BpiA5rAAGgdfc868fUR1sMwj0cm9Iu9IctweisViB3hhKTHDcHc5jv/LspbyaZrR1OD82/fIlOkuB9LnEWRmDX2TsddUPg3D5gvuc0je0rZaD5EW6G3yjS+A3eeBEWq3XW/Abw1HhUspXADufQb86oW7tZytkYCN//3hHwBvDALPi8EnSOYK8DAOfCc2h4aGcO7cuafkzampqf9UripH12/DtOZbx8ciVGzYy5OO40o25ascGRl5Ssc/AgwAjW3JwqIUjSYAAAAASUVORK5CYII=");
display: inline-block;
background-repeat: no-repeat;
line-height: 24px;
margin-top: 1px;
vertical-align: text-top;
}
@media (max-width: 603px) {
#container {
width: 300px;
margin: 10px auto;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
#container form.voucher-form input.voucher-code {
width: 180px;
}
}
@media (max-width: 603px) {
.reader-config-group {
width: 100%;
}
.reader-config-group label > span {
width: 50%;
}
.reader-config-group label > select, .reader-config-group label > input {
max-width: calc(50% - 2px);
}
#interactive.viewport {
width: 300px;
height: 300px;
overflow: hidden;
}
#interactive.viewport canvas, video {
margin-top: -50px;
width: 300px;
height: 400px;
}
#interactive.viewport canvas.drawingBuffer, video.drawingBuffer {
margin-left: -300px;
}
#result_strip {
margin-top: 5px;
padding-top: 5px;
}
#result_strip ul.thumbnails > li {
width: 150px;
}
#result_strip ul.thumbnails > li .thumbnail .imgWrapper {
width: 130px;
height: 130px;
overflow: hidden;
}
#result_strip ul.thumbnails > li .thumbnail .imgWrapper img {
margin-top: -25px;
width: 130px;
height: 180px;
}
}
@media (max-width: 603px) {
.overlay.scanner {
width: 640px;
height: 510px;
padding: 20px;
margin-top: -275px;
margin-left: -340px;
background-color: #FFF;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
.overlay.scanner > .header {
margin-bottom: 14px;
}
.overlay.scanner > .header h4, .overlay.scanner > .header .close {
line-height: 16px;
}
.overlay.scanner > .header .close {
height: 16px;
width: 16px;
}
}

View File

@@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<title>index</title>
<meta name="description" content=""/>
<meta name="author" content="Christoph Oberhofer"/>
<meta name="viewport" content="width=device-width; initial-scale=1.0"/>
<link rel="stylesheet" type="text/css" href="css/styles.css"/>
</head>
<body>
<header>
<div class="headline">
<h1>QuaggaJS</h1>
<h2>An advanced barcode-scanner written in JavaScript</h2>
</div>
</header>
<section id="container" class="container">
<h3>Working with file-input</h3>
<p>This example let's you select an image from your local filesystem.
QuaggaJS then tries to decode the barcode using
the preferred method (<strong>Code128</strong> or <strong>EAN</strong>).
There is no server interaction needed as the
file is simply accessed through the <a
href="http://www.w3.org/TR/file-upload/">File API</a>.</p>
<p>This also works great on a wide range of mobile-phones where the camera
access through <code>getUserMedia</code> is still very limited.</p>
<div class="controls">
<fieldset class="input-group">
<input type="file" accept="image/*" capture="camera"/>
<button>Rerun</button>
</fieldset>
<fieldset class="reader-config-group">
<label>
<span>Barcode-Type</span>
<select name="decoder_readers">
<option value="code_128" selected="selected">Code 128</option>
<option value="code_39">Code 39</option>
<option value="code_39_vin">Code 39 VIN</option>
<option value="ean">EAN</option>
<option value="ean_extended">EAN-extended</option>
<option value="ean_8">EAN-8</option>
<option value="upc">UPC</option>
<option value="upc_e">UPC-E</option>
<option value="codabar">Codabar</option>
<option value="i2of5">Interleaved 2 of 5</option>
<option value="2of5">Standard 2 of 5</option>
<option value="code_93">Code 93</option>
<option value="pharmacode">Pharmacode</option>
</select>
</label>
<label>
<span>Resolution (long side)</span>
<select name="input-stream_size">
<option value="320">320px</option>
<option value="640">640px</option>
<option selected="selected" value="800">800px</option>
<option value="1280">1280px</option>
<option value="1600">1600px</option>
<option value="1920">1920px</option>
</select>
</label>
<label>
<span>Patch-Size</span>
<select name="locator_patch-size">
<option value="x-small">x-small</option>
<option value="small">small</option>
<option value="medium">medium</option>
<option selected="selected" value="large">large</option>
<option value="x-large">x-large</option>
</select>
</label>
<label>
<span>Half-Sample</span>
<input type="checkbox" name="locator_half-sample" />
</label>
<label>
<span>Single Channel</span>
<input type="checkbox" name="input-stream_single-channel" />
</label>
<label>
<span>Locate (barcode finder)</span>
<input type="checkbox" checked="checked" name="locate" />
</label>
</fieldset>
</div>
<div id="result_strip">
<ul class="thumbnails"></ul>
</div>
<div id="interactive" class="viewport"></div>
<div id="debug" class="detection"></div>
</section>
<footer>
<p>
Quagga2 Project &copy; 2014-2025 Christoph Oberhofer, Eric Blade, and all other Contributors. See <a href="https://github.com/ericblade/quagga2/blob/master/package.json">package.json</a>. Licensed under MIT license. See <a href="https://github.com/ericblade/quagga2/blob/master/LICENSE">LICENSE</a>
</p>
</footer>
<script src="vendor/jquery-1.9.0.min.js" type="text/javascript"></script>
<script src="dist/quagga.min.js" type="text/javascript"></script>
<script src="file_input.js" type="text/javascript"></script>
</body>
</html>

View File

@@ -0,0 +1,203 @@
$(function() {
var App = {
init: function() {
App.syncCheckboxesFromState();
App.attachListeners();
},
syncCheckboxesFromState: function() {
var self = this;
$(".controls .reader-config-group input[type=checkbox]").each(function() {
var $checkbox = $(this),
name = $checkbox.attr("name"),
state = self._convertNameToState(name),
value = self._accessByPath(self.state, state);
$checkbox.prop("checked", !!value);
});
},
attachListeners: function() {
var self = this;
$(".controls input[type=file]").on("change", function(e) {
if (e.target.files && e.target.files.length) {
App.decode(URL.createObjectURL(e.target.files[0]));
}
});
$(".controls button").on("click", function(e) {
var input = document.querySelector(".controls input[type=file]");
if (input.files && input.files.length) {
App.decode(URL.createObjectURL(input.files[0]));
}
});
$(".controls .reader-config-group").on("change", "input, select", function(e) {
e.preventDefault();
var $target = $(e.target),
value = $target.attr("type") === "checkbox" ? $target.prop("checked") : $target.val(),
name = $target.attr("name"),
state = self._convertNameToState(name);
console.log("Value of "+ state + " changed to " + value);
self.setState(state, value);
});
},
_accessByPath: function(obj, path, val) {
var parts = path.split('.'),
depth = parts.length,
setter = (typeof val !== "undefined") ? true : false;
return parts.reduce(function(o, key, i) {
if (setter && (i + 1) === depth) {
o[key] = val;
}
return key in o ? o[key] : {};
}, obj);
},
_convertNameToState: function(name) {
return name.replace("_", ".").split("-").reduce(function(result, value) {
return result + value.charAt(0).toUpperCase() + value.substring(1);
});
},
detachListeners: function() {
$(".controls input[type=file]").off("change");
$(".controls .reader-config-group").off("change", "input, select");
$(".controls button").off("click");
},
decode: function(src) {
var self = this,
config = $.extend({}, self.state, {src: src});
Quagga.decodeSingle(config, function(result) {});
},
setState: function(path, value) {
var self = this;
if (typeof self._accessByPath(self.inputMapper, path) === "function") {
value = self._accessByPath(self.inputMapper, path)(value);
}
self._accessByPath(self.state, path, value);
console.log(JSON.stringify(self.state));
App.detachListeners();
App.init();
},
inputMapper: {
inputStream: {
size: function(value){
return parseInt(value);
}
},
decoder: {
readers: function(value) {
if (value === 'ean_extended') {
return [{
format: "ean_reader",
config: {
supplements: [
'ean_5_reader', 'ean_2_reader'
]
}
}];
}
return [{
format: value + "_reader",
config: {}
}];
}
}
},
state: {
inputStream: {
size: 800,
singleChannel: false,
area: {
top: "30%",
right: "10%",
left: "10%",
bottom: "30%",
backgroundColor: "rgba(255,0,0,0.15)"
}
},
locator: {
patchSize: "medium",
halfSample: true
},
decoder: {
readers: [{
format: "code_128_reader",
config: {}
}]
},
locate: true,
src: null
}
};
App.init();
function calculateRectFromArea(canvas, area) {
var canvasWidth = canvas.width,
canvasHeight = canvas.height,
top = parseInt(area.top)/100,
right = parseInt(area.right)/100,
bottom = parseInt(area.bottom)/100,
left = parseInt(area.left)/100;
top *= canvasHeight;
right = canvasWidth - canvasWidth*right;
bottom = canvasHeight - canvasHeight*bottom;
left *= canvasWidth;
return {
x: left,
y: top,
width: right - left,
height: bottom - top
};
}
Quagga.onProcessed(function(result) {
console.warn('* onProcessed', result);
var drawingCtx = Quagga.canvas.ctx.overlay,
drawingCanvas = Quagga.canvas.dom.overlay,
area;
if (result) {
if (result.boxes) {
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
result.boxes.filter(function (box) {
return box !== result.box;
}).forEach(function (box) {
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, drawingCtx, {color: "green", lineWidth: 2});
});
}
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, drawingCtx, {color: "#00F", lineWidth: 2});
}
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, drawingCtx, {color: 'red', lineWidth: 3});
}
if (App.state.inputStream.area) {
area = calculateRectFromArea(drawingCanvas, App.state.inputStream.area);
drawingCtx.strokeStyle = "#0F0";
drawingCtx.strokeRect(area.x, area.y, area.width, area.height);
}
}
});
Quagga.onDetected(function(result) {
console.warn('* onDetected', result);
var code = result.codeResult.code,
$node,
canvas = Quagga.canvas.dom.image;
$node = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>');
$node.find("img").attr("src", canvas.toDataURL());
$node.find("h4.code").html(code);
$("#result_strip ul.thumbnails").prepend($node);
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Quagga2 Live Examples</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="css/styles.css" />
</head>
<body>
<header>
<h1>Quagga2 Live Examples</h1>
<p>Choose an example to try out Quagga2 in your browser:</p>
</header>
<main>
<ul>
<li><a href="camera_example.html">Camera Example</a></li>
<li><a href="file_input.html">File Input Example</a></li>
<li><a href="live_w_locator.html">Live with Locator Example</a></li>
<li><a href="static_images.html">Static Images Example</a></li>
</ul>
<h2>Node.js Examples</h2>
<ul>
<li><a href="node-test.js">Node Test</a></li>
<li><a href="node-test-with-buffer.js">Node Test with Buffer</a></li>
</ul>
</main>
<footer>
<p>Quagga2 Project &copy; 2014-2025 Christoph Oberhofer, Eric Blade, and all other Contributors. See <a href="https://github.com/ericblade/quagga2/blob/master/package.json">package.json</a>. Licensed under MIT license. See <a href="https://github.com/ericblade/quagga2/blob/master/LICENSE">LICENSE</a></p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,116 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<title>index</title>
<meta name="description" content="" />
<meta name="author" content="Christoph Oberhofer" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
<link rel="stylesheet" type="text/css" href="css/styles.css" />
</head>
<body>
<header>
<div class="headline">
<h1>QuaggaJS</h1>
<h2>An advanced barcode-scanner written in JavaScript</h2>
</div>
</header>
<section id="container" class="container">
<h3>The user's camera</h3>
<p>If your platform supports the <strong>getUserMedia</strong> API call, you can try the real-time locating and decoding features.
Simply allow the page to access your web-cam and point it to a barcode. You can switch between <strong>Code128</strong>
and <strong>EAN</strong> to test different scenarios.
It works best if your camera has built-in auto-focus.
</p>
<div class="controls">
<fieldset class="input-group">
<button class="stop">Stop</button>
</fieldset>
<fieldset class="reader-config-group">
<label>
<span>Barcode-Type</span>
<select name="decoder_readers">
<option value="code_128" selected="selected">Code 128</option>
<option value="code_39">Code 39</option>
<option value="code_39_vin">Code 39 VIN</option>
<option value="ean">EAN</option>
<option value="ean_extended">EAN-extended</option>
<option value="ean_8">EAN-8</option>
<option value="upc">UPC</option>
<option value="upc_e">UPC-E</option>
<option value="codabar">Codabar</option>
<option value="i2of5">Interleaved 2 of 5</option>
<option value="2of5">Standard 2 of 5</option>
<option value="code_93">Code 93</option>
<option value="pharmacode">Pharmacode</option>
</select>
</label>
<label>
<span>Resolution (width)</span>
<select name="input-stream_constraints">
<option value="320x240">320px</option>
<option selected="selected" value="640x480">640px</option>
<option value="800x600">800px</option>
<option value="1280x720">1280px</option>
<option value="1600x960">1600px</option>
<option value="1920x1080">1920px</option>
</select>
</label>
<label>
<span>Patch-Size</span>
<select name="locator_patch-size">
<option value="x-small">x-small</option>
<option value="small">small</option>
<option selected="selected" value="medium">medium</option>
<option value="large">large</option>
<option value="x-large">x-large</option>
</select>
</label>
<label>
<span>Half-Sample</span>
<input type="checkbox" checked="checked" name="locator_half-sample" />
</label>
<label>
<span>Locate (barcode finder)</span>
<input type="checkbox" checked="checked" name="locate" />
</label>
<label>
<span>Camera</span>
<select name="input-stream_constraints" id="deviceSelection">
</select>
</label>
<label style="display: none">
<span>Zoom</span>
<select name="settings_zoom"></select>
</label>
<label style="display: none">
<span>Torch</span>
<input type="checkbox" name="settings_torch" />
</label>
</fieldset>
</div>
<div id="result_strip">
<ul class="thumbnails"></ul>
<ul class="collector"></ul>
</div>
<div id="interactive" class="viewport"></div>
</section>
<footer>
<p>
&copy; Made with ❤️ by Christoph Oberhofer
</p>
<p>
Quagga2 Project &copy; 2014-2025 Christoph Oberhofer, Eric Blade, and all other Contributors. See <a href="https://github.com/ericblade/quagga2/blob/master/package.json">package.json</a>. Licensed under MIT license. See <a href="https://github.com/ericblade/quagga2/blob/master/LICENSE">LICENSE</a>
</p>
</footer>
<script src="vendor/jquery-1.9.0.min.js" type="text/javascript"></script>
<script src="//webrtc.github.io/adapter/adapter-latest.js" type="text/javascript"></script>
<script src="dist/quagga.min.js" type="text/javascript"></script>
<script src="live_w_locator.js" type="text/javascript"></script>
</body>
</html>

View File

@@ -0,0 +1,402 @@
$(function() {
var resultCollector = Quagga.ResultCollector.create({
capture: true,
capacity: 20,
blacklist: [{
code: "WIWV8ETQZ1", format: "code_93"
}, {
code: "EH3C-%GU23RK3", format: "code_93"
}, {
code: "O308SIHQOXN5SA/PJ", format: "code_93"
}, {
code: "DG7Q$TV8JQ/EN", format: "code_93"
}, {
code: "VOFD1DB5A.1F6QU", format: "code_93"
}, {
code: "4SO64P4X8 U4YUU1T-", format: "code_93"
}],
filter: function(codeResult) {
// only store results which match this constraint
// e.g.: codeResult
return true;
}
});
var App = {
_initDebounceTimer: null,
_pendingReinit: false,
init: function() {
var self = this;
Quagga.init(this.state, function(err) {
if (err) {
return self.handleError(err);
}
//Quagga.registerResultCollector(resultCollector);
App.attachListeners();
Quagga.start();
App.initCameraSelection();
App.checkCapabilities();
// Sync UI checkboxes to match actual state after init
$('input[name="locate"]').prop('checked', App.state.locate);
});
},
reinit: function() {
// Debounced reinit: cancel pending and schedule a new one
console.log('[App.reinit] Called, current timer:', App._initDebounceTimer !== null, 'Current state.locate:', App.state.locate);
if (App._initDebounceTimer) {
console.log('[App.reinit] Cancelling pending init');
clearTimeout(App._initDebounceTimer);
}
App._initDebounceTimer = setTimeout(function() {
console.log('[App.reinit] Debounce expired, calling init() with state.locate:', App.state.locate);
App._initDebounceTimer = null;
App._pendingReinit = false;
App.init();
}, 250);
},
handleError: function(err) {
console.log(err);
// If we attempted to open a specific device and failed, revert to last known-good constraints
try {
var attempted = (App.state && App.state.inputStream && App.state.inputStream.constraints) || {};
var hadSpecificDevice = attempted && attempted.deviceId;
if (hadSpecificDevice) {
// Detach to avoid stacked handlers
// Try reinitializing to restore a working stream
Quagga.stop();
App.init();
}
} catch(e2) {
// Ignore and leave UI available for user to pick another device
}
},
checkCapabilities: function() {
var track = Quagga.CameraAccess.getActiveTrack();
var capabilities = {};
if (typeof track.getCapabilities === 'function') {
capabilities = track.getCapabilities();
}
this.applySettingsVisibility('zoom', capabilities.zoom);
this.applySettingsVisibility('torch', capabilities.torch);
},
updateOptionsForMediaRange: function(node, range) {
console.log('updateOptionsForMediaRange', node, range);
var NUM_STEPS = 6;
var stepSize = (range.max - range.min) / NUM_STEPS;
var option;
var value;
while (node.firstChild) {
node.removeChild(node.firstChild);
}
for (var i = 0; i <= NUM_STEPS; i++) {
value = range.min + (stepSize * i);
option = document.createElement('option');
option.value = value;
option.innerHTML = value;
node.appendChild(option);
}
},
applySettingsVisibility: function(setting, capability) {
// depending on type of capability
if (typeof capability === 'boolean') {
var node = document.querySelector('input[name="settings_' + setting + '"]');
if (node) {
node.parentNode.style.display = capability ? 'block' : 'none';
}
return;
}
if (window.MediaSettingsRange && capability instanceof window.MediaSettingsRange) {
var node = document.querySelector('select[name="settings_' + setting + '"]');
if (node) {
this.updateOptionsForMediaRange(node, capability);
node.parentNode.style.display = 'block';
}
return;
}
},
initCameraSelection: function(){
var streamLabel = Quagga.CameraAccess.getActiveStreamLabel();
var selectedDeviceId = null;
try {
// Prefer explicit deviceId from current state if present
var constraints = (this.state && this.state.inputStream && this.state.inputStream.constraints) || {};
if (constraints.deviceId) {
selectedDeviceId = (typeof constraints.deviceId === 'object' && constraints.deviceId.exact) ? constraints.deviceId.exact : constraints.deviceId;
} else {
selectedDeviceId = null;
}
} catch(e) {}
return Quagga.CameraAccess.enumerateVideoDevices()
.then(function(devices) {
function pruneText(text) {
return text.length > 30 ? text.substr(0, 30) : text;
}
var $deviceSelection = document.getElementById("deviceSelection");
while ($deviceSelection.firstChild) {
$deviceSelection.removeChild($deviceSelection.firstChild);
}
devices.forEach(function(device) {
var $option = document.createElement("option");
$option.value = device.deviceId || device.id;
$option.appendChild(document.createTextNode(pruneText(device.label || device.deviceId || device.id)));
// Preserve explicit selection by deviceId when available; fallback to stream label
if (selectedDeviceId) {
$option.selected = ($option.value === selectedDeviceId);
} else {
$option.selected = (streamLabel === device.label);
}
$deviceSelection.appendChild($option);
});
});
},
attachListeners: function() {
var self = this;
console.log('[App.attachListeners] Called - this should only happen once per init');
self.initCameraSelection();
// Remove any existing handlers to prevent stacking
$(".controls").off("click", "button.stop");
$(".controls .reader-config-group").off("change", "input, select");
$(".controls").on("click", "button.stop", function(e) {
e.preventDefault();
Quagga.stop();
self._printCollectedResults();
});
$(".controls .reader-config-group").on("change", "input, select", function(e) {
e.preventDefault();
var $target = $(e.target),
value = $target.attr("type") === "checkbox" ? $target.prop("checked") : $target.val(),
name = $target.attr("name"),
state = self._convertNameToState(name);
console.log("Value of "+ state + " changed to " + value, "checkbox checked prop:", $target.prop("checked"), "target type:", $target.attr("type"));
self.setState(state, value);
});
},
_printCollectedResults: function() {
var results = resultCollector.getResults(),
$ul = $("#result_strip ul.collector");
results.forEach(function(result) {
var $li = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>');
$li.find("img").attr("src", result.frame);
$li.find("h4.code").html(result.codeResult.code + " (" + result.codeResult.format + ")");
$ul.prepend($li);
});
},
_accessByPath: function(obj, path, val) {
var parts = path.split('.'),
depth = parts.length,
setter = (typeof val !== "undefined") ? true : false;
return parts.reduce(function(o, key, i) {
if (setter && (i + 1) === depth) {
if (typeof o[key] === "object" && typeof val === "object") {
Object.assign(o[key], val);
} else {
o[key] = val;
}
}
return key in o ? o[key] : {};
}, obj);
},
_convertNameToState: function(name) {
return name.replace("_", ".").split("-").reduce(function(result, value) {
return result + value.charAt(0).toUpperCase() + value.substring(1);
});
},
detachListeners: function() {
$(".controls").off("click", "button.stop");
$(".controls .reader-config-group").off("change", "input, select");
},
applySetting: function(setting, value) {
var track = Quagga.CameraAccess.getActiveTrack();
if (track && typeof track.getCapabilities === 'function') {
switch (setting) {
case 'zoom':
return track.applyConstraints({advanced: [{zoom: parseFloat(value)}]});
case 'torch':
return track.applyConstraints({advanced: [{torch: !!value}]});
}
}
},
setState: function(path, value) {
var self = this;
console.log('[App.setState] ENTRY: path=', path, 'value=', value, 'current state.locate=', self.state.locate);
if (typeof self._accessByPath(self.inputMapper, path) === "function") {
value = self._accessByPath(self.inputMapper, path)(value);
}
if (path.startsWith('settings.')) {
var setting = path.substring(9);
return self.applySetting(setting, value);
}
// If switching cameras, replace constraints entirely with { deviceId: { exact } }
if (path === 'inputStream.constraints' && value && typeof value === 'object' && 'deviceId' in value && Object.keys(value).length === 1) {
if (!self.state.inputStream) self.state.inputStream = {};
var dev = value.deviceId;
self.state.inputStream.constraints = { deviceId: (typeof dev === 'object' ? dev : { exact: dev }) };
} else {
self._accessByPath(self.state, path, value);
}
console.log('[App.setState] AFTER _accessByPath: state.locate=', self.state.locate);
console.log(JSON.stringify(self.state));
console.log('[App.setState] path:', path, 'pending:', App._pendingReinit);
// Prevent overlapping stop/reinit sequences
if (App._pendingReinit) {
// Already stopping/reiniting, just update the debounce timer
console.log('[App.setState] Already pending, just updating debounce');
App.reinit();
return;
}
console.log('[App.setState] Calling stop and scheduling reinit');
App._pendingReinit = true;
App.detachListeners();
var stopResult = Quagga.stop();
if (stopResult && typeof stopResult.then === 'function') {
stopResult.then(function(){
console.log('[App.setState] Stop completed (promise), calling reinit');
App.reinit();
});
} else {
// Older sync stop; reinit with debounce
console.log('[App.setState] Stop completed (sync), calling reinit');
App.reinit();
}
},
inputMapper: {
inputStream: {
constraints: function(value){
if (/^(\d+)x(\d+)$/.test(value)) {
var values = value.split('x');
// Update resolution while preserving any existing deviceId selection
var current = App.state && App.state.inputStream && App.state.inputStream.constraints || {};
var next = {
width: {min: parseInt(values[0])},
height: {min: parseInt(values[1])}
};
if (current.deviceId) {
next.deviceId = current.deviceId;
}
return next;
}
// Switching camera: use ONLY deviceId with exact match to avoid conflicting constraints
return { deviceId: { exact: value } };
}
},
decoder: {
readers: function(value) {
if (value === 'ean_extended') {
return [{
format: "ean_reader",
config: {
supplements: [
'ean_5_reader', 'ean_2_reader'
]
}
}];
}
return [{
format: value + "_reader",
config: {}
}];
}
}
},
state: {
inputStream: {
type : "LiveStream",
constraints: {
width: {min: 640},
height: {min: 480},
facingMode: "environment",
aspectRatio: {min: 1, max: 2}
},
area: {
top: "30%",
right: "10%",
left: "10%",
bottom: "30%",
// borderColor: "#0F0",
// borderWidth: 2,
backgroundColor: "rgba(255,0,0,0.15)"
}
},
locator: {
patchSize: "medium",
halfSample: true
},
frequency: 10,
decoder: {
readers : [{
format: "code_128_reader",
config: {}
}]
},
locate: true
},
lastResult : null
};
console.log('[Startup] Calling initial App.reinit()');
App.reinit();
var processedCount = 0;
Quagga.onProcessed(function(result) {
processedCount++;
/*
console.log('onProcessed #' + processedCount + ':', result ? {
hasBox: !!result.box,
boxLength: result.box ? result.box.length : 0,
hasBoxes: !!result.boxes,
boxesLength: result.boxes ? result.boxes.length : 0,
codeResult: !!result.codeResult
} : 'null result');
*/
var drawingCtx = Quagga.canvas.ctx.overlay,
drawingCanvas = Quagga.canvas.dom.overlay;
if (result) {
if (result.boxes) {
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
result.boxes.filter(function (box) {
return box !== result.box;
}).forEach(function (box) {
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, drawingCtx, {color: "orange", lineWidth: 2});
});
}
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, drawingCtx, {color: "#00F", lineWidth: 2});
}
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, drawingCtx, {color: 'red', lineWidth: 3});
}
}
});
Quagga.onDetected(function(result) {
var code = result.codeResult.code;
if (App.lastResult !== code) {
App.lastResult = code;
var $node = null, canvas = Quagga.canvas.dom.image;
$node = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>');
$node.find("img").attr("src", canvas.toDataURL());
$node.find("h4.code").html(code);
$("#result_strip ul.thumbnails").prepend($node);
}
});
});

View File

@@ -0,0 +1,27 @@
var Quagga = require('../lib/quagga').default;
var buffer = require('fs').readFileSync('../test/fixtures/code_128/image-001.jpg');
decode(buffer);
function decode(buff){
Quagga.decodeSingle({
src: buff,
numOfWorkers: 0,
inputStream: {
mime: "image/jpeg",
size: 800,
area: {
top: "10%",
right: "5%",
left: "5%",
bottom: "10%"
}
}
}, function(result) {
if (result.codeResult) {
console.log("result", result.codeResult.code);
} else {
console.log("not detected");
}
});
}

View File

@@ -0,0 +1,21 @@
var Quagga = require('../lib/quagga').default;
Quagga.decodeSingle({
src: "../test/fixtures/code_128/image-001.jpg",
numOfWorkers: 0,
inputStream: {
size: 800,
area: {
top: "10%",
right: "5%",
left: "5%",
bottom: "10%"
}
}
}, function(result) {
if(result.codeResult) {
console.log("result", result.codeResult.code);
} else {
console.log("not detected");
}
});

View File

@@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<title>index</title>
<meta name="description" content="" />
<meta name="author" content="Christoph Oberhofer" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href='http://fonts.googleapis.com/css?family=Ubuntu:400,700,300' rel='stylesheet' type='text/css'>
<link rel="stylesheet" type="text/css" href="css/styles.css" />
</head>
<body>
<header>
<div class="headline">
<h1>QuaggaJS</h1>
<h2>An advanced barcode-scanner written in JavaScript</h2>
</div>
</header>
<section id="container" class="container">
<h3>Working with static images</h3>
<p>This examples uses static image files as input which are loaded from the server on startup.
The locating and decoding process takes place inside the browser.
Hit the <strong>next</strong> button to try a different image. You can also switch between
two different test-sets. Each of those contains 10 images, demonstrating the capabilities of decoding
<strong>Code128</strong> and <strong>EAN</strong> encoded barcodes.
</p>
<div class="controls">
<fieldset class="input-group">
<button class="next">Next</button>
</fieldset>
<fieldset class="reader-config-group">
<span>Barcode-Type</span>
<select name="decoder_readers;input-stream_src">
<option value="code_128" selected="selected">Code 128</option>
<option value="code_39">Code 39</option>
<option value="code_39_vin">Code 39 VIN</option>
<option value="ean">EAN</option>
<option value="ean_extended">EAN-extended</option>
<option value="ean_8">EAN-8</option>
<option value="upc">UPC</option>
<option value="upc_e">UPC-E</option>
<option value="codabar">Codabar</option>
<option value="i2of5">I2of5</option>
<option value="i2of5">Interleaved 2 of 5</option>
<option value="2of5">Standard 2 of 5</option>
<option value="code_93">Code 93</option>
<option value="pharmacode">Pharmacode</option>
</select>
</fieldset>
</div>
<div id="result_strip">
<ul class="thumbnails"></ul>
</div>
<div id="interactive" class="viewport"></div>
<div id="debug" class="detection"></div>
</section>
<footer>
<p>
Quagga2 Project &copy; 2014-2025 Christoph Oberhofer, Eric Blade, and all other Contributors. See <a href="https://github.com/ericblade/quagga2/blob/master/package.json">package.json</a>. Licensed under MIT license. See <a href="https://github.com/ericblade/quagga2/blob/master/LICENSE">LICENSE</a>
</p>
</footer>
<script src="vendor/jquery-1.9.0.min.js" type="text/javascript"></script>
<script src="dist/quagga.min.js" type="text/javascript"></script>
<script src="static_images.js" type="text/javascript"></script>
</body>
</html>

View File

@@ -0,0 +1,177 @@
$(function() {
var App = {
init: function() {
var config = this.config[this.state.decoder.readers[0].format] || this.config.default;
config = $.extend(true, {}, config, this.state);
Quagga.init(config, function() {
App.attachListeners();
Quagga.start();
});
},
config: {
"default": {
inputStream: { name: "Test",
type: "ImageStream",
length: 10,
size: 800,
area: {
top: "30%",
right: "10%",
left: "10%",
bottom: "30%",
backgroundColor: "rgba(255,0,0,0.15)"
}
},
locator: {
patchSize: "medium",
halfSample: true
}
},
"i2of5_reader": {
inputStream: {
size: 800,
type: "ImageStream",
length: 5
},
locator: {
patchSize: "small",
halfSample: false
}
}
},
attachListeners: function() {
var self = this;
$(".controls").on("click", "button.next", function(e) {
e.preventDefault();
Quagga.start();
});
$(".controls .reader-config-group").on("change", "input, select", function(e) {
e.preventDefault();
var $target = $(e.target),
value = $target.attr("type") === "checkbox" ? $target.prop("checked") : $target.val(),
name = $target.attr("name"),
states = self._convertNameToStates(name);
console.log("Value of "+ states + " changed to " + value);
self.setState(states, value);
});
},
detachListeners: function() {
$(".controls").off("click", "button.next");
$(".controls .reader-config-group").off("change", "input, select");
},
_accessByPath: function(obj, path, val) {
var parts = path.split('.'),
depth = parts.length,
setter = (typeof val !== "undefined") ? true : false;
return parts.reduce(function(o, key, i) {
if (setter && (i + 1) === depth) {
o[key] = val;
}
return key in o ? o[key] : {};
}, obj);
},
_convertNameToStates: function(names) {
return names.split(";").map(this._convertNameToState.bind(this));
},
_convertNameToState: function(name) {
return name.replace("_", ".").split("-").reduce(function(result, value) {
return result + value.charAt(0).toUpperCase() + value.substring(1);
});
},
setState: function(paths, value) {
var self = this;
paths.forEach(function(path) {
var mappedValue;
if (typeof self._accessByPath(self.inputMapper, path) === "function") {
mappedValue = self._accessByPath(self.inputMapper, path)(value);
}
self._accessByPath(self.state, path, mappedValue);
});
console.log(JSON.stringify(self.state));
App.detachListeners();
Quagga.stop();
App.init();
},
inputMapper: {
decoder: {
readers: function(value) {
if (value === 'ean_extended') {
return [{
format: "ean_reader",
config: {
supplements: [
'ean_5_reader', 'ean_2_reader'
]
}
}];
}
return [{
format: value + "_reader",
config: {}
}];
}
},
inputStream: {
src: function(value) {
return "fixtures/" + value + "/"
}
}
},
state: {
inputStream: {
src: "fixtures/code_128/"
},
decoder : {
readers : [{
format: "code_128_reader",
config: {}
}]
}
}
};
App.init();
window.App = App;
Quagga.onProcessed(function(result) {
var drawingCtx = Quagga.canvas.ctx.overlay,
drawingCanvas = Quagga.canvas.dom.overlay;
if (result) {
if (result.boxes) {
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
result.boxes.filter(function (box) {
return box !== result.box;
}).forEach(function (box) {
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, drawingCtx, {color: "green", lineWidth: 2});
});
}
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, drawingCtx, {color: "#00F", lineWidth: 2});
}
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, drawingCtx, {color: 'red', lineWidth: 3});
}
}
});
Quagga.onDetected(function(result) {
var $node,
canvas = Quagga.canvas.dom.image,
detectedCode = result.codeResult.code;
$node = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>');
$node.find("img").attr("src", canvas.toDataURL());
$node.find("h4.code").html(detectedCode);
$("#result_strip ul.thumbnails").prepend($node);
});
});

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,69 @@
# Algorithm Overview {#algorithm-overview}
This page provides a high-level overview of how Quagga2 processes images to detect and decode barcodes.
## Processing Pipeline {#processing-pipeline}
Quagga2 processes each frame through a multi-stage pipeline:
```
Input Image → Preprocessing → Localization → Decoding → Result
```
### 1. Preprocessing {#preprocessing}
- **Scaling**: Image is resized based on `inputStream.size`
- **Grayscale conversion**: Color image converted to grayscale
- **Area cropping**: If `inputStream.area` is set, crop to that region
### 2. Localization {#localization}
When `locate: true` (default):
1. **Binarization**: Convert to black/white using Otsu's method
2. **Grid division**: Split image into patches
3. **Skeletonization**: Extract line structures
4. **Pattern analysis**: Find barcode-like patterns
5. **Bounding box**: Calculate barcode region
See [How Barcode Localization Works](how-barcode-localization-works.md) for detailed explanation.
### 3. Decoding {#decoding}
1. **Scanline extraction**: Sample pixels along detected barcode
2. **Pattern matching**: Match bar/space patterns to barcode format
3. **Character decoding**: Convert patterns to characters
4. **Checksum validation**: Verify barcode integrity
## Key Algorithms {#key-algorithms}
### Otsu's Method {#otsus-method}
Automatic threshold selection for binarization. Adapts to varying lighting conditions by analyzing the image histogram.
### Connected Component Labeling {#connected-component-labeling}
Groups adjacent pixels into distinct regions. Used during localization to identify potential barcode patterns.
### Image Moments {#image-moments}
Mathematical technique to calculate orientation and position of detected patterns.
## Performance Characteristics {#performance-characteristics}
| Factor | Impact | Mitigation |
|--------|--------|------------|
| Image size | Linear increase | Use `inputStream.size` |
| Number of readers | Linear increase | Only enable needed readers |
| Localization | ~60% of processing time | Use `locate: false` if position known |
| Half sampling | 4x faster | Keep `halfSample: true` |
## Related {#related}
- [How Barcode Localization Works](how-barcode-localization-works.md) - Detailed localization explanation
- [Architecture](architecture.md) - Code structure overview
- [Optimize Performance](../how-to-guides/optimize-performance.md) - Performance tuning
---
[← Back to Explanation](index.md)

View File

@@ -0,0 +1,117 @@
# Architecture {#architecture}
This page describes Quagga2's code structure and design decisions.
## Project Structure {#project-structure}
```
quagga2/
├── src/
│ ├── quagga.ts # Main entry point
│ ├── config/ # Configuration handling
│ ├── decoder/ # Barcode decoding
│ │ ├── barcode_decoder.ts
│ │ └── readers/ # Individual barcode readers
│ ├── locator/ # Barcode localization
│ │ ├── barcode_locator.ts
│ │ └── skeletonizer.ts
│ ├── input/ # Input handling
│ │ ├── camera_access.ts
│ │ └── frame_grabber.ts
│ └── common/ # Shared utilities
│ ├── cv_utils.ts # Computer vision utilities
│ └── image_wrapper.ts
├── dist/ # Browser builds
├── lib/ # Node.js build
└── type-definitions/ # TypeScript types
```
## Core Components {#core-components}
### Quagga (Main API) {#main-api}
The main entry point (`src/quagga.ts`) exposes the public API:
- `init()` / `start()` / `stop()`
- `decodeSingle()`
- `onDetected()` / `onProcessed()`
- `CameraAccess` namespace
### Barcode Locator {#barcode-locator}
Located in `src/locator/`, responsible for finding barcode regions in images:
- **barcode_locator.ts**: Main localization logic
- **skeletonizer.ts**: Line structure extraction (asm.js optimized)
### Barcode Decoder {#barcode-decoder}
Located in `src/decoder/`, handles barcode decoding:
- **barcode_decoder.ts**: Coordinates decoding process
- **readers/**: Individual reader implementations
- Each reader extends `BarcodeReader` base class
- Implements format-specific pattern matching
### Input Handling {#input-handling}
Located in `src/input/`:
- **camera_access.ts**: Camera enumeration and control
- **frame_grabber_browser.ts**: Browser frame capture
- **frame_grabber_node.ts**: Node.js image loading
## Data Flow {#data-flow}
```
Camera/Image
FrameGrabber (captures frame)
ImageWrapper (grayscale conversion)
BarcodeLocator (finds barcode region)
BarcodeDecoder (decodes barcode)
Result callbacks
```
## Design Decisions {#design-decisions}
### Bundle Everything {#bundle-everything}
All dependencies are bundled into the final build. This means:
- Consumers never install dependencies directly
- All packages go in `devDependencies`
- Builds are self-contained
### Dual Build Targets {#dual-build-targets}
- **Browser**: `dist/quagga.min.js` - UMD bundle
- **Node.js**: `lib/quagga.js` - CommonJS module
### Reader Architecture {#reader-architecture}
Readers are pluggable:
- Built-in readers in `src/decoder/readers/`
- External readers via `Quagga.registerReader()`
- All readers extend common `BarcodeReader` class
## Build System {#build-system}
- **Webpack 4**: Module bundling
- **Babel**: ES6+ transpilation
- **TypeScript**: Type checking (but source is mixed JS/TS)
## Related {#related}
- [Algorithm Overview](algorithm-overview.md) - How the algorithms work
- [Create External Readers](../how-to-guides/external-readers.md) - Extend Quagga2
- [Contributing](../contributing.md) - Development setup
---
[← Back to Explanation](index.md)

View File

@@ -0,0 +1,197 @@
# How Barcode Localization Works {#how-barcode-localization-works}
> **Note on Terminology**: "Localization" in this context refers to finding the **physical location** (position, bounding box) of a barcode within an image - not language localization (i18n). This is standard computer vision terminology.
This article explains the technical details of how Quagga2 locates and decodes barcodes in images. Understanding this can help you optimize performance and troubleshoot issues.
## Overview {#overview}
Quagga2 uses a two-stage process:
1. **Barcode Locator** (blue box in the images below) - Finds regions that look like barcodes
2. **Barcode Decoder** (red line in the images below) - Reads the actual barcode data
This approach is based on the paper [Locating and decoding EAN-13 barcodes from images captured by digital cameras](http://www.icics.org/2005/download/P0840.pdf) by Douglas et al., with adaptations and modifications for web browsers.
## Stage 1: Barcode Locator {#stage-1-barcode-locator}
The locator finds patterns that look like barcodes. A barcode is typically characterized by:
- **Lines** (black bars and white gaps)
- That are **close to each other**
- With a **similar angle** (parallel lines)
### Step 1: Creating a Binary Image {#step-1-binary-image}
The first step is converting the color image to binary (black and white). Instead of using a simple threshold (e.g., everything below 127 is black), Quagga2 uses **Otsu's method**, which adapts to lighting changes across the image.
![Binary Image](../assets/binary.png)
Otsu's method analyzes the image histogram and automatically separates foreground (barcode) from background, even with uneven lighting.
### Step 2: Slicing into a Grid {#step-2-slicing-grid}
The binary image is divided into a **20×15 grid** (assuming 4:3 aspect ratio). Each cell is analyzed independently to determine if it contains barcode-like patterns.
### Step 3: Extract Skeleton {#step-3-extract-skeleton}
Each cell undergoes **skeletonization** - reducing bars to their centerline (1px width). This is done through iterative erosion and dilation.
![Skeleton Image](../assets/skeleton.png)
The skeleton clearly shows where parallel lines exist, making it easier to identify barcode regions.
### Step 4: Component Labeling {#step-4-component-labeling}
Using **connected-component labeling**, each line in the skeletonized image is separated into individual components. This is done with a fast algorithm based on the paper ["A Linear-Time Component-Labeling Algorithm Using Contour Tracing Technique"](http://www.iis.sinica.edu.tw/papers/fchang/1362-F.pdf) by Fu Chang et al.
![Component Labeling](../assets/component-labeling.png)
Each color represents a distinct labeled component (line). Notice how each cell is processed independently.
Here are zoomed examples of two cells:
![Component labeling - barcode lines](../assets/component-labeling-line.png)
*Good: Parallel lines indicate a possible barcode*
![Component labeling - text](../assets/component-labeling-text.png)
*Bad: Random components indicate noise/text*
### Step 5: Determining Orientation {#step-5-determining-orientation}
For each component, Quagga2 calculates its orientation using **central image moments**. This is a mathematical technique to extract the angle of a shape.
The orientation (θ) is calculated as:
![Theta calculation](http://www.sciweavers.org/tex2img.php?eq=%5CTheta%20%3D%20%5Cfrac%7B1%7D%7B2%7D%20%5Carctan%20%5Cleft%28%20%5Cfrac%7B2%5Cmu%27_%7B11%7D%7D%7B%5Cmu%27_%7B20%7D%20-%20%5Cmu%27_%7B02%7D%7D%20%5Cright%29&bc=Transparent&fc=Black&im=png&fs=12&ff=arev&edit=0)
Where μ (mu) are central moments calculated from raw moments (M):
![Mu calculation](http://www.sciweavers.org/tex2img.php?eq=%5Cmu%27_%7B11%7D%20%3D%20M_%7B11%7D%2FM_%7B00%7D%20-%20%5Cbar%7Bx%7D%5Cbar%7By%7D%20%5C%5C%0A%5Cmu%27_%7B02%7D%20%3D%20M_%7B02%7D%2FM_%7B00%7D%20-%20%5Cbar%7By%7D%5E2%20%5C%5C%0A%5Cmu%27_%7B20%7D%20%3D%20M_%7B20%7D%2FM_%7B00%7D%20-%20%5Cbar%7Bx%7D%5E2&bc=Transparent&fc=Black&im=png&fs=12&ff=arev&edit=0)
The centroid (x̄, ȳ) is calculated from raw moments:
![X Y bar calculation](http://www.sciweavers.org/tex2img.php?eq=%5Cbar%7Bx%7D%20%3D%20M_%7B10%7D%2FM_%7B00%7D%20%5C%5C%0A%5Cbar%7By%7D%20%3D%20M_%7B01%7D%2FM_%7B00%7D&bc=Transparent&fc=Black&im=png&fs=12&ff=arev&edit=0)
And the raw moments (M) are computed as:
![M calculation](http://www.sciweavers.org/tex2img.php?eq=M_%7B00%7D%20%3D%20%5Csum_x%20%5Csum_y%20I%28x%2Cy%29%20%5C%5C%0AM_%7B10%7D%20%3D%20%5Csum_x%20%5Csum_y%20x%20I%28x%2Cy%29%20%5C%5C%0AM_%7B01%7D%20%3D%20%5Csum_x%20%5Csum_y%20y%20I%28x%2Cy%29%20%5C%5C%0AM_%7B11%7D%20%3D%20%5Csum_x%20%5Csum_y%20xy%20I%28x%2Cy%29%20%5C%5C%0AM_%7B20%7D%20%3D%20%5Csum_x%20%5Csum_y%20x%5E2%20I%28x%2Cy%29%20%5C%5C%0AM_%7B02%7D%20%3D%20%5Csum_x%20%5Csum_y%20y%5E2%20I%28x%2Cy%29&bc=Transparent&fc=Black&im=png&fs=12&ff=arev&edit=0)
Where I(x,y) is the pixel value at position (x,y) - either 0 or 1 in a binary image.
Don't worry if the math looks intimidating - the key insight is that these formulas calculate **which direction each line is pointing**.
### Step 6: Determining Cell Quality {#step-6-determining-cell-quality}
Cells are evaluated based on how parallel their lines are:
1. **Filter out noise**: Discard cells with fewer than 2 components, or components smaller than 6 pixels
2. **Cluster angles**: Group similar angles together
3. **Select dominant cluster**: Pick the cluster with the most members
4. **Quality threshold**: Only accept cells where ≥75% of components share the same angle
Cells that pass this test are called **patches** and contain:
- Unique index
- Bounding box
- All components with their angles
- Average angle
- Direction vector
![Found patches](../assets/patches_found.png)
Yellow boxes show patches that were classified as possible barcode areas. Note some false positives (text regions).
### Step 7: Finding Connected Cells {#step-7-finding-connected-cells}
Patches are grouped together if they're neighbors with similar orientation (within 5% angle difference). This is done using recursive component labeling.
![Connected patch labels](../assets/connected-patch-labels.png)
Each color represents a distinct group. Sometimes adjacent patches have different colors due to angle differences exceeding the 5% threshold.
### Step 8: Selecting Groups {#step-8-selecting-groups}
Groups are sorted by size (number of patches) and only the largest groups are kept - these are most likely to be actual barcodes.
![Remaining patch labels](../assets/remaining-patch-labels.png)
Small groups and false positives have been filtered out.
### Step 9: Create Bounding Box {#step-9-create-bounding-box}
For each group, a **minimum bounding box** is calculated:
1. Calculate average angle of all patches in the group
2. Rotate all patches by this angle
3. Find outermost corners (min/max x and y)
4. Create bounding box
5. Rotate box back to original orientation
![Rotated cells with box](../assets/bb-rotated.png)
*Patches rotated to horizontal, bounding box calculated*
![Bounding box](../assets/bb-binary.png)
*Final bounding box rotated back to match barcode orientation*
The bounding box now precisely outlines the barcode, including its rotation and scale. This information is passed to the decoder.
## Stage 2: Barcode Decoder {#stage-2-barcode-decoder}
With the bounding box and orientation known, the decoder:
1. Samples pixel intensities along scan lines within the box
2. Detects transitions from black to white (edges of bars)
3. Calculates bar widths
4. Matches patterns against the selected barcode format(s)
5. Validates checksums
6. Returns the decoded data
## Why This Approach? {#why-this-approach}
Unlike simpler barcode scanners that require the barcode to be:
- Horizontal (aligned with the viewport)
- At a specific distance (scale)
- Centered in the frame
Quagga2's localization algorithm is **invariant to rotation and scale**. It can find and decode barcodes:
- At any angle
- At any size (within reason)
- Anywhere in the image
- Even with multiple barcodes present
This makes it much more practical for real-world camera scanning where users can't always position the camera perfectly.
## Performance Considerations {#performance-considerations}
The localization algorithm is computationally intensive. Key factors affecting performance:
- **Image size**: Smaller images process faster. Consider downscaling if speed is critical.
- **Grid size**: The default 20×15 grid balances accuracy and speed
- **Patch filtering**: Aggressive filtering reduces false positives but may miss difficult barcodes
- **Number of readers**: Enabling multiple barcode formats increases decode time
See [Optimize Performance](../how-to-guides/optimize-performance.md) for practical tips.
## Related Reading {#related-reading}
- **Original Paper**: [Locating and decoding EAN-13 barcodes from images captured by digital cameras](http://www.icics.org/2005/download/P0840.pdf)
- **Otsu's Method**: [Wikipedia - Otsu's method](http://en.wikipedia.org/wiki/Otsu%27s_method)
- **Skeletonization**: [Wikipedia - Morphological skeleton](http://en.wikipedia.org/wiki/Morphological_skeleton)
- **Component Labeling**: [Wikipedia - Connected-component labeling](http://en.wikipedia.org/wiki/Connected-component_labeling)
- **Image Moments**: [Wikipedia - Image moment](http://en.wikipedia.org/wiki/Image_moment#Central_moments)
- **Fast Labeling Algorithm**: [A Linear-Time Component-Labeling Algorithm (PDF)](http://www.iis.sinica.edu.tw/papers/fchang/1362-F.pdf)
- **CodeProject Implementation**: [Connected Component Labeling and Vectorization](http://www.codeproject.com/Tips/407172/Connected-Component-Labeling-and-Vectorization)
## Source Code {#source-code}
The localization algorithm is implemented in:
- `src/locator/barcode_locator.js` - Main locator logic
- `src/locator/skeletonizer.js` - Skeletonization algorithm (asm.js)
- `src/common/cluster.js` - Clustering algorithms
- `src/common/cv_utils.js` - Computer vision utilities
Contributions and improvements are welcome!

View File

@@ -0,0 +1,80 @@
# Explanation
Background knowledge and context about Quagga2. These articles explain *why* things work the way they do, providing deeper understanding beyond just how to use the library.
## Algorithm & Theory
### [How Barcode Localization Works](how-barcode-localization-works.md) ⭐ Featured
Deep dive into the computer vision algorithms that find barcodes in images. Covers:
- Binary image creation with Otsu's method
- Skeletonization
- Component labeling
- Orientation detection using image moments
- Why this approach is rotation/scale invariant
**Read this if**: You want to understand the "magic" behind barcode detection, optimize performance, or contribute to the localization code.
## Architecture
### [How Input Streams Work](input-streams.md)
Technical deep dive into the three input stream types and their initialization flow. Covers:
- LiveStream, VideoStream, and ImageStream differences
- The async initialization sequence
- Why `framegrabber` indicates init completion
- Race conditions and how to avoid them
**Read this if**: You're debugging initialization issues, dealing with React StrictMode, or want to understand the media pipeline.
### [Algorithm Overview](algorithm-overview.md) *(Coming Soon)*
High-level overview of Quagga2's processing pipeline from input to output.
### [Code Architecture](architecture.md)
Structure of the codebase, module organization, and design decisions.
## Concepts
### [Why asm.js for Skeletonization](why-asmjs.md) *(Coming Soon)*
Explanation of why performance-critical code uses asm.js and what that means.
### [Reader Design Patterns](reader-patterns.md) *(Coming Soon)*
How barcode readers are implemented and how to create custom ones.
## Differences from Other Doc Types
**Explanation** articles:
- Provide context and background
- Discuss alternatives and trade-offs
- Explain historical decisions
- Connect concepts together
- Are OK to read casually
**Tutorials**: Step-by-step learning by doing
**How-To Guides**: Task-focused, get things done
**Reference**: Precise technical specifications
## When to Read Explanations
Read these articles when you:
- Want to **understand** how things work under the hood
- Need to **debug** complex issues
- Want to **contribute** to the codebase
- Are **curious** about design decisions
- Want to **optimize** beyond basic tuning
## Contributing
Have insights to share about how Quagga2 works? We welcome explanation articles! See the [Contributing Guide](../contributing.md).
---
[← Back to Documentation Home](../index.md)

View File

@@ -0,0 +1,270 @@
# How Input Streams Work {#how-input-streams-work}
This article explains the technical details of how Quagga2's input stream system works. Understanding this is helpful for troubleshooting initialization issues and understanding async behavior.
## Overview {#overview}
Quagga2 supports three types of input streams for reading barcode data:
| Type | Use Case | Input Source |
|------|----------|--------------|
| **LiveStream** | Real-time camera scanning | Device camera via getUserMedia |
| **VideoStream** | Pre-recorded video files | Video file via `<video>` element |
| **ImageStream** | Static images or image sequences | Image file(s) via URL |
All three stream types share the same interface (`InputStream`) and follow a common initialization pattern, but differ in how they acquire media.
## The InputStream Interface {#inputstream-interface}
Every input stream implements these core methods:
```typescript
interface InputStream {
// Dimensions
getWidth(): number;
getHeight(): number;
getRealWidth(): number;
getRealHeight(): number;
setWidth(width: number): void;
setHeight(height: number): void;
// Frame access
getFrame(): HTMLVideoElement | HTMLImageElement | null;
// Event handling
addEventListener(event: string, handler: Function): void;
clearEventHandlers(): void;
trigger(eventName: string, args?: any): void;
// Playback control
play(): void;
pause(): void;
ended(): boolean;
// Configuration
setInputStream(config: any): void;
getConfig(): any;
}
```
## Initialization Flow {#initialization-flow}
All stream types follow the same initialization sequence:
```
init() → initInputStream() → [async media access] → 'canrecord' event → canRecord() → framegrabber created
```
Here's what happens at each step:
### 1. `init()` is called {#step-1-init}
The static `Quagga.init(config, callback)` function starts the process:
```javascript
Quagga.init({
inputStream: {
type: 'LiveStream', // or 'VideoStream' or 'ImageStream'
target: document.querySelector('#scanner'),
// ... other options
},
// ... decoder config
}, (err) => {
if (err) {
console.error('Init failed:', err);
return;
}
Quagga.start();
});
```
### 2. `initInputStream()` creates the stream {#step-2-initinputstream}
Based on the `type` configuration, the appropriate stream factory is called:
- `LiveStream``createLiveStream(video)`
- `VideoStream``createVideoStream(video)`
- `ImageStream``createImageStream()`
### 3. Async media access begins {#step-3-async-media}
This is where the streams diverge:
**LiveStream**: Calls `CameraAccess.request()` which uses `navigator.mediaDevices.getUserMedia()`. This is async because:
- Browser shows a permission prompt
- Camera hardware needs to spin up
- Video dimensions aren't known until stream starts
**VideoStream**: Creates a `<video>` element and waits for the video to load metadata. Async because the video file must be fetched.
**ImageStream**: Uses `ImageLoader` to fetch and decode image(s). Async because images must be downloaded.
### 4. `canrecord` event fires {#step-4-canrecord}
When the media is ready, the stream triggers the `canrecord` event. This is the signal that:
- Media dimensions are now available
- Frames can be grabbed
- Processing can begin
### 5. `canRecord()` completes initialization {#step-5-canrecord-callback}
The `canRecord()` callback:
1. Validates the input stream is properly initialized
2. Calls `checkImageConstraints()` to validate/adjust dimensions
3. Creates the canvas for drawing frames
4. Creates the **framegrabber** (the component that extracts frames)
5. Sets up worker threads (if configured)
6. Calls the user's callback to signal init is complete
### 6. Framegrabber indicates completion {#step-6-framegrabber}
The `framegrabber` being non-null is the reliable indicator that initialization completed successfully. This is why:
- The static `start()` function checks `if (!_context.framegrabber)` before proceeding
- The `stop()` function uses `!framegrabber` to detect if init was still in progress
## Stream Type Details {#stream-type-details}
### LiveStream {#livestream}
**Purpose**: Real-time barcode scanning using the device camera.
**How it works**:
1. Creates or finds a `<video>` element in the target container
2. Requests camera access via `getUserMedia()`
3. Attaches the camera stream to the video element
4. Sets `autoplay="true"` so the video starts immediately
5. Triggers `canrecord` when camera is ready
**Key characteristics**:
- `ended()` always returns `false` (camera never "ends")
- Requires HTTPS in production (browser security requirement)
- Can specify camera constraints (facing mode, resolution)
**Configuration example**:
```javascript
inputStream: {
type: 'LiveStream',
target: document.querySelector('#camera'),
constraints: {
facingMode: 'environment', // Back camera
width: { min: 640 },
height: { min: 480 }
}
}
```
### VideoStream {#videostream}
**Purpose**: Scanning barcodes from pre-recorded video files.
**How it works**:
1. Creates a new `<video>` element
2. Sets the `src` attribute to the video URL
3. Waits for video metadata to load
4. Triggers `canrecord` when dimensions are known
**Key characteristics**:
- `ended()` returns the video element's ended state
- Supports seeking via `setCurrentTime()`
- Video plays frame-by-frame during scanning
**Configuration example**:
```javascript
inputStream: {
type: 'VideoStream',
src: '/path/to/video.mp4'
}
```
### ImageStream {#imagestream}
**Purpose**: Scanning barcodes from static images or image sequences.
**How it works**:
1. Parses the image URL configuration
2. Uses `ImageLoader` to fetch the image(s)
3. Reads EXIF data to handle image orientation
4. Calculates dimensions based on size config
5. Triggers `canrecord` when image(s) are loaded
**Key characteristics**:
- Can process a single image or a sequence
- Handles EXIF orientation automatically
- `ended()` returns true after all images are processed
- Used internally by `decodeSingle()`
**Configuration example (single image)**:
```javascript
inputStream: {
type: 'ImageStream',
src: '/path/to/barcode.jpg',
sequence: false
}
```
**Configuration example (image sequence)**:
```javascript
inputStream: {
type: 'ImageStream',
src: '/path/to/images/img_%d.jpg', // %d is replaced with frame number
sequence: true,
length: 10 // Number of images
}
```
## Race Conditions and Async Behavior {#race-conditions}
Because initialization involves async operations (camera access, file loading), race conditions can occur if:
1. **`stop()` is called during `init()`**: The `canrecord` event may fire after `stop()` has begun cleanup. Quagga2 handles this with an `initAborted` flag.
2. **React StrictMode double-invocation**: StrictMode mounts, unmounts, and remounts components, causing rapid `init() → stop() → init()` sequences.
3. **Component unmounting before camera ready**: User navigates away before `getUserMedia()` resolves.
**Best practices to avoid issues**:
```javascript
useLayoutEffect(() => {
let cancelled = false;
Quagga.init(config, (err) => {
if (cancelled) return; // Ignore if unmounted
if (err) {
console.error(err);
return;
}
Quagga.start();
});
return () => {
cancelled = true;
Quagga.stop();
};
}, []);
```
## Source Code {#source-code}
The input stream system is implemented in:
- `src/input/input_stream/input_stream_browser.ts` - Browser stream implementations
- `src/input/input_stream/input_stream.ts` - Node.js stream implementation
- `src/input/input_stream/input_stream.d.ts` - TypeScript interface
- `src/quagga/setupInputStream.ts` - Stream factory selection
- `src/input/camera_access.ts` - Camera permission handling
- `src/input/frame_grabber.js` - Frame extraction for Node.js (uses ndarray)
- `src/input/frame_grabber_browser.js` - Frame extraction for browsers (uses canvas)
> **Note**: Webpack replaces `frame_grabber.js` with `frame_grabber_browser.js` when building the browser bundle. The Node.js version uses `ndarray` for image manipulation, while the browser version uses the Canvas API.
## Related Reading {#related-reading}
- [How Barcode Localization Works](how-barcode-localization-works.md) - What happens after frames are grabbed
- [Camera Access Reference](../reference/camera-access.md) - Camera configuration options
- [Configuration Reference](../reference/configuration.md) - Full config documentation
---
[← Back to Explanation Index](index.md)

View File

@@ -0,0 +1,213 @@
# Getting Started with Quagga2 {#getting-started}
This guide will help you install Quagga2 and get your first barcode scanner running quickly.
## What is Quagga2? {#what-is-quagga2}
Quagga2 is a JavaScript barcode scanner library that works in both browsers and Node.js. It can:
- **Scan barcodes in real-time** using your device's camera
- **Decode barcodes from images** (photos, screenshots, etc.)
- **Support multiple formats**: EAN, CODE 128, CODE 39, EAN 8, UPC-A, UPC-C, I2of5, 2of5, CODE 93, CODE 32, CODABAR, and PHARMACODE
- **Work offline** - all processing happens in the browser/Node.js, no server required
Unlike some libraries, Quagga2 includes a barcode **locator** that can find and decode barcodes regardless of their rotation or scale in the image.
## Installation {#installation}
### Using NPM (Recommended) {#using-npm}
```bash
npm install --save @ericblade/quagga2
```
Then import it in your project:
```javascript
// ES6 modules
import Quagga from '@ericblade/quagga2';
// CommonJS (important: note the .default)
const Quagga = require('@ericblade/quagga2').default;
```
### Using CDN (Script Tag) {#using-cdn}
Add one of these script tags to your HTML:
**Unminified version** (useful for development):
```html
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.js"></script>
```
**Minified version** (recommended for production):
```html
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.min.js"></script>
```
**Specific version** (recommended to avoid breaking changes):
```html
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2@1.8.4/dist/quagga.min.js"></script>
```
The script tag exposes the library globally as `Quagga`.
## Basic Usage - Live Camera Scanning {#live-camera-scanning}
Here's a minimal example to scan barcodes using your device's camera:
### HTML {#html-example}
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Quagga2 Barcode Scanner</title>
</head>
<body>
<div id="scanner-container"></div>
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.min.js"></script>
<script src="app.js"></script>
</body>
</html>
```
### JavaScript (app.js) {#javascript-example}
```javascript
Quagga.init({
inputStream: {
type: "LiveStream",
target: document.querySelector('#scanner-container')
},
decoder: {
readers: ["code_128_reader"]
}
}, function(err) {
if (err) {
console.error(err);
return;
}
console.log("Initialization finished. Ready to start");
Quagga.start();
});
Quagga.onDetected(function(result) {
const code = result.codeResult.code;
console.log("Barcode detected:", code);
alert("Found barcode: " + code);
});
```
That's it! This will:
1. Request camera access
2. Display the camera feed in `#scanner-container`
3. Continuously scan for CODE 128 barcodes
4. Alert you when a barcode is detected
## Basic Usage - Static Image Scanning {#static-image-scanning}
To decode a barcode from an existing image:
```javascript
Quagga.decodeSingle({
src: "path/to/your/image.jpg",
decoder: {
readers: ["code_128_reader"]
}
// Note: Images are scaled to 800px (longest side) by default.
// See inputStream.size in the Configuration Reference for details.
}, function(result) {
if (result && result.codeResult) {
console.log("Barcode found:", result.codeResult.code);
} else {
console.log("No barcode found");
}
});
```
## Important Notes {#important-notes}
### HTTPS Required {#https-required}
For security reasons, browsers require **HTTPS** to access the camera, except on `localhost`. If you host your app on a domain, you must use `https://`.
### Browser Compatibility {#browser-compatibility}
Quagga2 works in modern browsers that support:
- Canvas API
- Typed Arrays
- MediaDevices API (for camera access)
See the [Browser Support](reference/browser-support.md) page for detailed compatibility information.
### Polyfill Recommendation {#polyfill-recommendation}
Different browsers implement camera APIs differently. We recommend including [webrtc-adapter](https://github.com/webrtc/adapter) for better compatibility:
```html
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
```
## Next Steps {#next-steps}
Now that you have the basics working:
- **[Try a complete tutorial](tutorials/first-scan.md)** - Build a working barcode scanner step-by-step
- **[Learn about configuration options](reference/configuration.md)** - Customize behavior
- **[Explore supported barcode types](reference/readers.md)** - Enable different formats
- **[Check out live examples](https://serratus.github.io/quaggaJS/examples)** - See Quagga2 in action
## Common Issues {#common-issues}
### Camera Permission Denied {#camera-permission-denied}
If the user denies camera access, the `init` callback will receive an error. Always handle this gracefully:
```javascript
Quagga.init(config, function(err) {
if (err) {
if (err.name === 'NotAllowedError') {
alert('Please allow camera access to scan barcodes');
}
console.error(err);
return;
}
Quagga.start();
});
```
### Camera Not Found {#camera-not-found}
Some devices don't have a camera (desktops, VMs). Check for camera availability:
```javascript
if (navigator.mediaDevices && typeof navigator.mediaDevices.getUserMedia === 'function') {
// Camera API available
Quagga.init(config, callback);
} else {
// Fallback to file upload
alert('Camera not available, please upload an image');
}
```
### No Barcode Detected {#no-barcode-detected}
If Quagga2 isn't detecting your barcode:
- Make sure you've enabled the correct reader (e.g., `ean_reader` for EAN-13)
- Ensure good lighting
- Try holding the barcode steady and filling most of the camera view
- See [How to Handle Difficult Barcodes](how-to-guides/handle-difficult-barcodes.md)
## Getting Help {#getting-help}
- **[GitHub Issues](https://github.com/ericblade/quagga2/issues)** - Report bugs or request features
- **[Gitter Chat](https://gitter.im/quaggaJS/Lobby)** - Ask questions and get help from the community
- **[API Documentation](reference/api.md)** - Complete API reference

View File

@@ -0,0 +1,79 @@
# Configure Barcode Readers {#configure-readers}
This guide explains how to configure which barcode formats Quagga2 will detect.
## Basic Configuration {#basic-configuration}
Specify readers in the `decoder.readers` array:
```javascript
Quagga.init({
decoder: {
readers: ["code_128_reader", "ean_reader"]
}
});
```
## Available Readers {#available-readers}
| Reader | Format | Common Uses |
|--------|--------|-------------|
| `code_128_reader` | Code 128 | Shipping, packaging |
| `ean_reader` | EAN-13 | Retail products |
| `ean_8_reader` | EAN-8 | Small products |
| `upc_reader` | UPC-A | North American retail |
| `upc_e_reader` | UPC-E | Small products |
| `code_39_reader` | Code 39 | Industrial |
| `code_39_vin_reader` | Code 39 VIN | Vehicle identification |
| `codabar_reader` | Codabar | Libraries, blood banks |
| `i2of5_reader` | Interleaved 2 of 5 | Warehouse |
| `2of5_reader` | Standard 2 of 5 | Industrial |
| `code_93_reader` | Code 93 | Logistics |
| `code_32_reader` | Code 32 | Italian pharmaceuticals |
## Reader Priority {#reader-priority}
Readers are processed in order. Put most likely formats first:
```javascript
decoder: {
readers: [
"ean_reader", // Most common - checked first
"upc_reader", // Second most common
"code_128_reader" // Fallback
]
}
```
## EAN Supplements {#ean-supplements}
To read EAN-2 or EAN-5 extension supplements:
```javascript
decoder: {
readers: [
"ean_reader", // Regular EAN without supplements
{
format: "ean_reader",
config: {
supplements: ["ean_5_reader", "ean_2_reader"]
}
}
]
}
```
## Best Practices {#best-practices}
1. **Only enable needed readers** - More readers = slower performance
2. **Order by frequency** - Put most common formats first
3. **Test thoroughly** - Some formats can conflict
## Related {#related}
- [Supported Barcode Types](../reference/readers.md) - Complete reader documentation
- [Configuration Reference](../reference/configuration.md) - All configuration options
---
[← Back to How-To Guides](index.md)

View File

@@ -0,0 +1,234 @@
# How-To: Create and Use External Readers {#create-and-use-external-readers}
This guide explains how to create custom barcode reader plugins and integrate them into Quagga2.
## Using Existing External Readers {#using-existing-external-readers}
### QR Code Reader {#qr-code-reader}
The most common external reader is [quagga2-reader-qr](https://github.com/ericblade/quagga2-reader-qr) for QR code support:
```javascript
import Quagga from '@ericblade/quagga2';
import QRReader from 'quagga2-reader-qr';
// Register the external reader
Quagga.registerReader('qr', QRReader);
// Use it in your config
Quagga.init({
decoder: {
readers: ['qr']
}
});
```
## External Reader Priority {#external-reader-priority}
External readers follow the **same priority rules** as built-in readers:
1. **Registration first**: Call `Quagga.registerReader(name, ReaderClass)` before using the reader
2. **Position determines priority**: The reader's position in the `readers` array determines when it attempts to decode
3. **First success wins**: The first reader to return a valid result is used
### Example: Prioritizing External Readers {#prioritizing-external-readers}
```javascript
// Register external reader
Quagga.registerReader('my_custom_reader', MyCustomReader);
// External reader tried first, then built-in readers
Quagga.init({
decoder: {
readers: ['my_custom_reader', 'ean_reader', 'code_128_reader']
}
});
```
### Example: External Reader as Fallback {#external-reader-fallback}
```javascript
// Register external reader
Quagga.registerReader('my_fallback_reader', MyFallbackReader);
// Built-in readers tried first, external as fallback
Quagga.init({
decoder: {
readers: ['ean_reader', 'code_128_reader', 'my_fallback_reader']
}
});
```
## Creating Custom Readers {#creating-custom-readers}
Quagga2 exports the `BarcodeReader` prototype that you can extend to create custom readers.
### Basic Reader Structure {#basic-reader-structure}
```javascript
import { Readers } from '@ericblade/quagga2';
const { BarcodeReader } = Readers;
class MyCustomReader extends BarcodeReader {
FORMAT = 'my_custom_format';
constructor(config, supplements) {
super(config, supplements);
// Custom initialization
}
decode(row, start) {
// Implement barcode decoding logic
// row: Array<number> - binary line data
// start: number - starting position
// Return null if no barcode found
// Return Barcode object if successful
return null;
}
}
export default MyCustomReader;
```
### Reader Return Format {#reader-return-format}
Your `decode()` method should return either `null` (no match) or a result object:
```javascript
{
code: '1234567890', // The decoded barcode value
start: 0, // Start position in the row
end: 100, // End position in the row
startInfo: { start: 0, end: 10 }, // Start pattern info
format: 'my_custom_format', // Your reader's format name
decodedCodes: [...] // Optional: decoded character info
}
```
### Pattern Matching Utilities {#pattern-matching-utilities}
BarcodeReader provides useful methods for pattern matching:
- `_nextSet(line, offset)` - Find next set (1) bit
- `_nextUnset(line, offset)` - Find next unset (0) bit
- `_matchPattern(counter, code, maxSingleError)` - Match bar patterns
- `_fillCounters(offset, end, isWhite)` - Count consecutive bars/spaces
### Example: Simple Pattern Reader {#simple-pattern-reader}
```javascript
class SimpleBarReader extends BarcodeReader {
FORMAT = 'simple_bar';
START_PATTERN = [1, 1, 1]; // Three bars pattern
decode(row, start) {
if (row) {
this._row = row;
}
// Find start pattern
const startInfo = this._findPattern(this.START_PATTERN, 0, false);
if (!startInfo) {
return null;
}
// Decode the barcode content
const result = this._decodeContent(startInfo.end);
if (!result) {
return null;
}
return {
code: result.code,
start: startInfo.start,
end: result.end,
startInfo: startInfo,
format: this.FORMAT
};
}
_decodeContent(offset) {
// Implement your decoding logic
return null;
}
}
```
## Image-Based Readers {#image-based-readers}
For non-linear barcodes (like QR codes), implement `decodeImage()` instead:
```javascript
class MyImageReader extends BarcodeReader {
FORMAT = 'my_image_format';
// Override decode to return null (not a linear barcode)
decode() {
return null;
}
// Implement image-based decoding
async decodeImage(imageWrapper) {
// imageWrapper.data - pixel data
// imageWrapper.size - { x, y } dimensions
// Process the image and decode
const result = await this.processImage(imageWrapper);
if (!result) {
return null;
}
return {
codeResult: {
code: result.data,
format: this.FORMAT
}
};
}
}
```
## Testing Your Reader {#testing-your-reader}
1. **Unit tests**: Test pattern matching and decoding logic in isolation
2. **Integration tests**: Use `Quagga.decodeSingle()` with test images
3. **Live testing**: Test with real camera input
### Example Test {#example-test}
```javascript
import Quagga from '@ericblade/quagga2';
import MyCustomReader from './my-custom-reader';
Quagga.registerReader('my_custom', MyCustomReader);
const result = await Quagga.decodeSingle({
src: './test-image.jpg',
decoder: {
readers: ['my_custom']
}
});
console.log('Result:', result?.codeResult?.code);
```
## Best Practices {#best-practices}
1. **Set unique FORMAT**: Use a distinctive format name to identify your reader
2. **Handle edge cases**: Return `null` gracefully when patterns don't match
3. **Validate checksums**: Implement checksum validation when the format supports it
4. **Consider performance**: Optimize pattern matching for real-time scanning
5. **Test thoroughly**: Test with various image qualities and conditions
## Related {#related}
- [Supported Barcode Types](../reference/readers.md) - Built-in readers
- [Configuration Reference](../reference/configuration.md) - Full config options
- [quagga2-reader-qr](https://github.com/ericblade/quagga2-reader-qr) - Example external reader
---
[← Back to How-To Guides](index.md)

View File

@@ -0,0 +1,127 @@
# Handle Difficult Barcodes {#handle-difficult-barcodes}
This guide provides techniques for improving barcode detection in challenging conditions.
## Common Challenges {#common-challenges}
- Poor lighting or shadows
- Blurry or out-of-focus images
- Small or distant barcodes
- Damaged or partially obscured barcodes
- Low contrast between bars and background
## Improving Detection {#improving-detection}
### Adjust Patch Size {#adjust-patch-size}
For small or distant barcodes, use a smaller patch size:
```javascript
locator: {
patchSize: "small" // or "x-small" for very small barcodes
}
```
### Increase Resolution {#increase-resolution}
Higher resolution provides more detail:
```javascript
inputStream: {
size: 1280, // Larger processing size
constraints: {
width: { ideal: 1920 },
height: { ideal: 1080 }
}
}
```
### Disable Half Sampling {#disable-half-sampling}
For fine details, process at full resolution:
```javascript
locator: {
halfSample: false
}
```
## Handling False Positives {#handling-false-positives}
### Validate Results {#validate-results}
Check result confidence and format:
```javascript
Quagga.onDetected(function(result) {
// Check if result has expected format
if (result.codeResult.format !== 'ean_13') {
return; // Ignore unexpected formats
}
// Validate checksum externally if needed
if (!validateBarcode(result.codeResult.code)) {
return;
}
processBarcode(result.codeResult.code);
});
```
### Require Multiple Reads {#require-multiple-reads}
Confirm detection across multiple frames:
```javascript
let lastCode = null;
let readCount = 0;
Quagga.onDetected(function(result) {
const code = result.codeResult.code;
if (code === lastCode) {
readCount++;
if (readCount >= 3) {
// Confirmed detection
processBarcode(code);
readCount = 0;
}
} else {
lastCode = code;
readCount = 1;
}
});
```
## Using Debug Flags {#using-debug-flags}
Enable visual debugging to understand detection issues:
```javascript
Quagga.init({
debug: true,
decoder: {
debug: {
drawBoundingBox: true,
drawScanline: true
}
},
locator: {
debug: {
showFoundPatches: true
}
}
});
```
See [Use Debug Flags](use-debug-flags.md) for complete details.
## Related {#related}
- [Configuration Reference](../reference/configuration.md) - All configuration options
- [Optimize Performance](optimize-performance.md) - Balance accuracy vs speed
- [Use Debug Flags](use-debug-flags.md) - Diagnostic tools
---
[← Back to How-To Guides](index.md)

View File

@@ -0,0 +1,77 @@
# How-To Guides
Task-oriented guides for solving specific problems with Quagga2. These assume you have basic familiarity with the library and focus on getting things done.
## Configuration & Setup
### [Configure Barcode Readers](configure-readers.md)
Select which barcode formats to scan (EAN, CODE 128, UPC, etc.) and understand the trade-offs.
### [Use Debug Flags](use-debug-flags.md) 🆕
Enable diagnostic output to troubleshoot issues with localization, decoding, or camera setup.
## Performance & Quality
### [Optimize Performance](optimize-performance.md)
Improve scanning speed and reduce CPU usage through configuration tuning.
### [Handle Difficult Barcodes](handle-difficult-barcodes.md)
Techniques for decoding barcodes with poor lighting, damage, rotation, or small size.
## Advanced Features
### [Create External Readers](external-readers.md)
Build custom barcode decoder plugins to support additional formats.
## Camera & Input
### [Select Specific Camera](select-camera.md) *(Coming Soon)*
Choose between front/back cameras on mobile devices or select from multiple USB cameras.
### [Adjust Camera Settings](camera-settings.md) *(Coming Soon)*
Control focus, zoom, flash/torch, and other camera parameters.
## Drawing & Visualization
### [Working with Box Coordinates](working-with-coordinates.md) 🆕
Understand how `box`, `boxes`, and `line` coordinates relate to processed vs. original image dimensions, and how to properly scale coordinates for overlay rendering on video elements.
## Tips & Best Practices
### [Tips and Tricks](tips-and-tricks.md)
Practical advice for getting the best results with Quagga2, including camera setup, user experience, and handling results.
## Integration
### [Handle Multiple Barcodes](multiple-barcodes.md) *(Coming Soon)*
Detect and decode several barcodes in a single frame simultaneously.
### [Scan in Background](background-scanning.md) *(Coming Soon)*
Continue scanning while users interact with other UI elements.
## Differences from Tutorials
**Tutorials** are for learning - they're comprehensive, beginner-friendly, and explain *why* you're doing each step.
**How-To Guides** are for doing - they're focused, assume knowledge, and show *how* to accomplish specific goals.
If you're new to Quagga2, start with [Tutorials](../tutorials/) instead.
## Contributing
Have a useful technique or solution to share? We welcome contributions! See the [Contributing Guide](../contributing.md).
---
[← Back to Documentation Home](../index.md)

View File

@@ -0,0 +1,114 @@
# Optimize Performance {#optimize-performance}
This guide covers techniques to improve Quagga2's barcode scanning performance.
## Overview {#overview}
Performance optimization in Quagga2 involves balancing accuracy against speed. The key areas to optimize are:
- Input resolution and scaling
- Locator configuration
- Reader selection
- Processing frequency
## Input Resolution {#input-resolution}
### Using `inputStream.size` {#inputstream-size}
Reducing the processing resolution is the most effective way to improve performance:
```javascript
Quagga.init({
inputStream: {
size: 640 // Process at 640px max dimension instead of full resolution
}
});
```
**Recommended values:**
- **1280px** - High quality, slower (good for static images)
- **800px** - Balanced (default for `decodeSingle`)
- **640px** - Fast (recommended for live scanning)
- **480px** - Very fast (may reduce accuracy)
### Camera Constraints {#camera-constraints}
Request only the resolution you need:
```javascript
inputStream: {
constraints: {
width: { ideal: 1280 },
height: { ideal: 720 }
}
}
```
## Locator Configuration {#locator-configuration}
### Half Sampling {#half-sampling}
Keep `halfSample: true` (default) for faster localization:
```javascript
locator: {
halfSample: true // Processes at half resolution
}
```
### Patch Size {#patch-size}
Larger patch sizes are faster but may miss small barcodes:
```javascript
locator: {
patchSize: "large" // Options: x-small, small, medium, large, x-large
}
```
## Reader Selection {#reader-selection}
Only enable the barcode formats you need:
```javascript
decoder: {
readers: ["code_128_reader"] // Don't enable all readers
}
```
## Processing Frequency {#processing-frequency}
Limit scan rate to reduce CPU usage:
```javascript
Quagga.init({
frequency: 10 // Max 10 scans per second
});
```
## Disable Localization {#disable-localization}
If barcode position is fixed, disable localization entirely:
```javascript
Quagga.init({
locate: false,
inputStream: {
area: {
top: "25%",
right: "25%",
bottom: "25%",
left: "25%"
}
}
});
```
## Related {#related}
- [Configuration Reference](../reference/configuration.md) - All configuration options
- [How Barcode Localization Works](../explanation/how-barcode-localization-works.md) - Understanding the algorithm
---
[← Back to How-To Guides](index.md)

View File

@@ -0,0 +1,115 @@
# Tips and Tricks {#tips-and-tricks}
Practical advice for getting the best results with Quagga2.
## Camera Setup {#camera-setup}
### Choosing the Right Camera {#choosing-camera}
- Use the **back camera** on mobile devices (higher resolution, autofocus)
- Request environment-facing camera: `facingMode: "environment"`
- Avoid wide-angle cameras for barcode scanning
### Optimal Distance {#optimal-distance}
- Barcode should fill 50-80% of the frame width
- Too close: barcode may be out of focus
- Too far: insufficient resolution for small bars
## Lighting {#lighting}
- Ensure even lighting across the barcode
- Avoid harsh shadows or reflections
- Enable torch/flash for dark environments:
```javascript
await Quagga.CameraAccess.enableTorch();
```
## User Experience {#user-experience}
### Visual Feedback {#visual-feedback}
Show users the scan area:
```javascript
inputStream: {
area: {
top: "25%",
right: "10%",
bottom: "25%",
left: "10%",
borderColor: "rgba(0, 255, 0, 0.7)",
borderWidth: 2
}
}
```
### Audio Feedback {#audio-feedback}
Play a sound on successful scan:
```javascript
Quagga.onDetected(function(result) {
new Audio('/beep.mp3').play();
processBarcode(result.codeResult.code);
});
```
## Performance {#performance}
### Reduce CPU Usage {#reduce-cpu-usage}
```javascript
Quagga.init({
frequency: 10, // Limit to 10 scans/second
inputStream: {
size: 640 // Reduce processing resolution
}
});
```
### Stop When Not Needed {#stop-when-not-needed}
```javascript
// Stop scanning when modal closes
Quagga.stop();
// Remove event handlers
Quagga.offDetected();
Quagga.offProcessed();
```
## Handling Results {#handling-results}
### Debounce Detections {#debounce-detections}
Avoid processing the same barcode multiple times:
```javascript
let lastScanned = '';
let lastTime = 0;
Quagga.onDetected(function(result) {
const code = result.codeResult.code;
const now = Date.now();
if (code === lastScanned && now - lastTime < 2000) {
return; // Same barcode within 2 seconds
}
lastScanned = code;
lastTime = now;
processBarcode(code);
});
```
## Related {#related}
- [Optimize Performance](optimize-performance.md) - Detailed performance guide
- [Handle Difficult Barcodes](handle-difficult-barcodes.md) - Improve detection
- [Camera Access API](../reference/camera-access.md) - Camera control
---
[← Back to How-To Guides](index.md)

View File

@@ -0,0 +1,776 @@
# How to Use Debug Flags {#how-to-use-debug-flags}
Quagga2 includes several debug flags that enable diagnostic console output to help troubleshoot issues with barcode detection and decoding. This guide explains when and how to use them.
## Overview {#overview}
Debug flags control what information Quagga2 logs to the browser console. By default, **all debug output is suppressed** to keep your console clean. Enable specific flags when you need to diagnose problems.
## Important Note {#important-note}
Debug flags only work when `ENV.development` is `true` (development builds). Production builds strip out all debug code to minimize bundle size.
## Available Debug Flags {#available-debug-flags}
Debug flags are organized into three categories:
1. **Console logging flags** - Print diagnostic information to the console
2. **Visual canvas overlays** - Draw debugging information on the canvas
3. **Performance analysis** - Display frequency and pattern data
### Console Logging Flags {#console-logging-flags}
#### `inputStream.debug.showImageDetails` {#inputstream-debug-showimagedetails}
**What it shows**: Image loading and frame grabber operations
**Console output example**:
```text
*** frame_grabber_browser: willReadFrequency=undefined canvas=<canvas>
Image Loader: Loaded 3 images from /path/to/image.jpg
```
**When to use**:
- Camera feed not showing
- Images not loading from file input
- Canvas-related issues
**How to enable**:
```javascript
Quagga.init({
inputStream: {
// ... your input config
debug: {
showImageDetails: true
}
},
// ... rest of config
});
```
#### `decoder.debug.printReaderInfo` {#decoder-debug-printreaderinfo}
**What it shows**: Barcode reader registration and initialization
**Console output example**:
```text
* ImageWrapper getCanvasAndContext
Registering reader: code_128_reader
Before registering reader: EANReader
Registered Readers: code_128, ean
```
**When to use**:
- Verifying which readers are active
- Reader not detecting expected barcode type
- Multiple reader configuration issues
**How to enable**:
```javascript
Quagga.init({
decoder: {
readers: ["code_128_reader", "ean_reader"],
debug: {
printReaderInfo: true
}
},
// ... rest of config
});
```
#### `locator.debug.showPatchSize` {#locator-debug-showpatchsize}
**What it shows**: Patch dimensions during barcode localization
**Console output example**:
```text
Patch-Size: 320x240
```
**When to use**:
- Barcode locator not finding barcodes
- Understanding what image size the locator is processing
- Performance optimization (smaller patches = faster)
**How to enable**:
```javascript
Quagga.init({
locator: {
patchSize: "medium",
debug: {
showPatchSize: true
}
},
// ... rest of config
});
```
#### `locator.debug.showImageDetails` {#locator-debug-showimagedetails}
**What it shows**: Canvas and image wrapper initialization for locator
**Console output example**:
```text
* initCanvas getCanvasAndContext
* ImageWrapper getCanvasAndContext
```
**When to use**:
- Locator canvas not rendering
- Debugging locator initialization failures
- Understanding when canvas contexts are created
**How to enable**:
```javascript
Quagga.init({
locator: {
// ... your locator config
debug: {
showImageDetails: true
}
},
// ... rest of config
});
```
### Visual Canvas Overlay Flags {#visual-canvas-overlay-flags}
These flags draw debugging information directly on the canvas, allowing you to visualize the barcode detection algorithm's internal state.
Note: The scan area overlay (defined via `inputStream.area`) is not a debug flag. When `locate: false`, Quagga draws the scan area on the overlay canvas each processed frame. You can also draw it manually using `Quagga.drawScannerArea()`. See the [Configuration Reference](../reference/configuration.md#inputstream-area-drawing) and [API](../reference/api.md#quagga-drawscannerarea).
#### `decoder.debug.drawBoundingBox` {#decoder-debug-drawboundingbox}
**What it shows**: Draws a box around the detected barcode location
**When to use**:
- Verify barcode is being located correctly
- Debug positioning issues
- Understand where the decoder thinks the barcode is
**How to enable**:
```javascript
Quagga.init({
decoder: {
debug: {
drawBoundingBox: true
}
},
// ... rest of config
});
```
#### `decoder.debug.drawScanline` {#decoder-debug-drawscanline}
**What it shows**: Draws the scanline path used for decoding
**When to use**:
- Verify scanner is reading through the barcode correctly
- Debug angle/orientation issues
- Understand why certain barcodes fail to decode
**How to enable**:
```javascript
Quagga.init({
decoder: {
debug: {
drawScanline: true
}
},
// ... rest of config
});
```
#### `locator.debug.showCanvas` {#locator-debug-showcanvas}
**What it shows**: Displays the locator's internal canvas used for image processing
**When to use**:
- Debug localization algorithm
- Verify image preprocessing is working
- Understand what the locator "sees"
**How to enable**:
```javascript
Quagga.init({
locator: {
debug: {
showCanvas: true
}
},
// ... rest of config
});
```
#### `locator.debug.showPatches` {#locator-debug-showpatches}
**What it shows**: Draws all patches extracted during the localization phase
**When to use**:
- Debug patch extraction issues
- Verify patch size is appropriate
- Understand what regions are being analyzed
**How to enable**:
```javascript
Quagga.init({
locator: {
debug: {
showPatches: true
}
},
// ... rest of config
});
```
#### `locator.debug.showFoundPatches` {#locator-debug-showfoundpatches}
**What it shows**: Highlights patches where potential barcodes were found
**When to use**:
- Verify barcode candidates are being identified
- Debug false positives/negatives in patch detection
- Optimize patch threshold settings
**How to enable**:
```javascript
Quagga.init({
locator: {
debug: {
showFoundPatches: true
}
},
// ... rest of config
});
```
#### `locator.debug.showSkeleton` {#locator-debug-showskeleton}
**What it shows**: Displays the skeleton structure extracted from patches
**When to use**:
- Debug advanced localization algorithm
- Understand structure detection
- Verify skeleton extraction is working
**How to enable**:
```javascript
Quagga.init({
locator: {
debug: {
showSkeleton: true
}
},
// ... rest of config
});
```
#### `locator.debug.showLabels` {#locator-debug-showlabels}
**What it shows**: Displays component labels during connected component analysis
**When to use**:
- Debug component labeling phase
- Verify components are being identified correctly
- Understand clustering behavior
**How to enable**:
```javascript
Quagga.init({
locator: {
debug: {
showLabels: true
}
},
// ... rest of config
});
```
#### `locator.debug.showPatchLabels` {#locator-debug-showpatchlabels}
**What it shows**: Shows labels assigned to individual patches
**When to use**:
- Debug patch classification
- Verify patches are being labeled correctly
- Understand patch grouping
**How to enable**:
```javascript
Quagga.init({
locator: {
debug: {
showPatchLabels: true
}
},
// ... rest of config
});
```
#### `locator.debug.showRemainingPatchLabels` {#locator-debug-showremainingpatchlabels}
**What it shows**: Displays labels for patches remaining after filtering
**When to use**:
- Debug patch filtering logic
- Verify correct patches survive filtering
- Optimize filter thresholds
**How to enable**:
```javascript
Quagga.init({
locator: {
debug: {
showRemainingPatchLabels: true
}
},
// ... rest of config
});
```
#### `locator.debug.boxFromPatches.showTransformed` {#locator-debug-boxfrompatches-showtransformed}
**What it shows**: Shows transformed patch coordinates during box calculation
**When to use**:
- Debug coordinate transformation
- Verify spatial transformations are correct
- Understand box calculation from patches
**How to enable**:
```javascript
Quagga.init({
locator: {
debug: {
boxFromPatches: {
showTransformed: true
}
}
},
// ... rest of config
});
```
#### `locator.debug.boxFromPatches.showTransformedBox` {#locator-debug-boxfrompatches-showtransformedbox}
**What it shows**: Displays the bounding box after transformation
**When to use**:
- Debug box transformation
- Verify box coordinates after spatial transform
- Understand final box positioning
**How to enable**:
```javascript
Quagga.init({
locator: {
debug: {
boxFromPatches: {
showTransformedBox: true
}
}
},
// ... rest of config
});
```
#### `locator.debug.boxFromPatches.showBB` {#locator-debug-boxfrompatches-showbb}
**What it shows**: Displays the final bounding box around detected barcode region
**When to use**:
- Verify final bounding box is correct
- Debug box calculation from patches
- Optimize localization accuracy
**How to enable**:
```javascript
Quagga.init({
locator: {
debug: {
boxFromPatches: {
showBB: true
}
}
},
// ... rest of config
});
```
### Performance Analysis Flags {#performance-analysis-flags}
#### `decoder.debug.showFrequency` {#decoder-debug-showfrequency}
**What it shows**: Displays frequency data from the barcode scanline
**When to use**:
- Analyze barcode signal quality
- Debug decoding issues at the signal level
- Understand why certain barcodes fail to decode
**How to enable**:
```javascript
Quagga.init({
decoder: {
debug: {
showFrequency: true
}
},
// ... rest of config
});
```
#### `decoder.debug.showPattern` {#decoder-debug-showpattern}
**What it shows**: Displays the pattern data extracted from the barcode
**When to use**:
- Analyze pattern recognition issues
- Debug specific barcode format problems
- Understand pattern extraction process
**How to enable**:
```javascript
Quagga.init({
decoder: {
debug: {
showPattern: true
}
},
// ... rest of config
});
```
## Common Debugging Scenarios {#common-debugging-scenarios}
### "No barcodes detected" {#no-barcodes-detected}
Enable console logging first, then add visual overlays if needed:
```javascript
Quagga.init({
inputStream: {
target: document.querySelector('#scanner'),
debug: {
showImageDetails: true
}
},
decoder: {
readers: ["code_128_reader"],
debug: {
printReaderInfo: true,
drawBoundingBox: true, // Visual: see if barcode is located
drawScanline: true // Visual: see scan path
}
},
locator: {
debug: {
showPatchSize: true,
showImageDetails: true,
showFoundPatches: true // Visual: see candidate patches
}
}
}, function(err) {
if (err) {
console.error("Init error:", err);
return;
}
console.log("Starting Quagga...");
Quagga.start();
});
```
Check console for:
- ✅ Readers registered correctly?
- ✅ Images loading?
- ✅ Patch size reasonable (not 0x0)?
- ✅ Canvas contexts created?
Check canvas overlay for:
- ✅ Are patches being detected? (green highlights)
- ✅ Is a bounding box drawn?
- ✅ Is the scanline visible and passing through the barcode?
### "Camera not working" {#camera-not-working}
Enable input stream debugging:
```javascript
Quagga.init({
inputStream: {
type: "LiveStream",
debug: {
showImageDetails: true // Shows camera/canvas setup
}
},
// ... rest of config
});
```
### "Wrong barcode type detected" {#wrong-barcode-type}
Enable reader info to verify configuration:
```javascript
Quagga.init({
decoder: {
readers: [
"code_128_reader", // Did you enable the right readers?
"ean_reader"
],
debug: {
printReaderInfo: true // Shows which readers are active
}
},
// ... rest of config
});
```
### "Deep-dive localization debugging" {#deep-dive-debugging}
For advanced debugging of the localization algorithm, enable all visual overlays:
```javascript
Quagga.init({
locator: {
debug: {
showCanvas: true,
showPatches: true,
showFoundPatches: true,
showSkeleton: true,
showLabels: true,
showPatchLabels: true,
showRemainingPatchLabels: true,
boxFromPatches: {
showTransformed: true,
showTransformedBox: true,
showBB: true
}
}
}
}, function(err) {
if (err) {
console.error(err);
return;
}
Quagga.start();
});
```
This will display every step of the localization process visually on the canvas. Use this to:
- Understand the complete localization pipeline
- Optimize patch size and thresholds
- Debug complex barcode positioning issues
- Learn how the algorithm works
**Warning**: Enabling all visual overlays may impact performance and make the canvas cluttered. Enable only what you need.
## Performance Impact {#performance-impact}
Debug flags have varying performance impacts:
**Console logging flags** (minimal impact):
- `showImageDetails`, `printReaderInfo`, `showPatchSize` - negligible overhead
- Only execute when enabled and only in development builds
**Visual canvas overlays** (moderate to high impact):
- Drawing operations on canvas can slow down real-time detection
- More overlays = more draw calls = slower performance
- Consider disabling in production or using only for development/debugging
**Performance analysis flags** (high impact):
- `showFrequency`, `showPattern` - can generate large amounts of data
- Best used sparingly when diagnosing specific decoding issues
**Recommendation**: Enable only the flags you need. Disable all visual overlays for production.
## Disabling Debug Output {#disabling-debug-output}
To turn off all debug output, either:
**Option 1**: Remove debug properties entirely
```javascript
Quagga.init({
inputStream: {
// debug property removed
},
// ...
});
```
**Option 2**: Set flags to `false`
```javascript
Quagga.init({
inputStream: {
debug: {
showImageDetails: false
}
},
decoder: {
debug: {
printReaderInfo: false,
drawBoundingBox: false,
drawScanline: false,
showFrequency: false,
showPattern: false
}
},
locator: {
debug: {
showCanvas: false,
showPatches: false,
showFoundPatches: false,
showSkeleton: false,
showLabels: false,
showPatchLabels: false,
showRemainingPatchLabels: false,
showPatchSize: false,
showImageDetails: false,
boxFromPatches: {
showTransformed: false,
showTransformedBox: false,
showBB: false
}
}
}
});
```
## Using in Node.js {#using-in-nodejs}
Debug flags work in Node.js too! Output goes to `console.log`:
```javascript
const Quagga = require('@ericblade/quagga2').default;
Quagga.decodeSingle({
src: './barcode.jpg',
decoder: {
readers: ['code_128_reader'],
debug: {
printReaderInfo: true // Shows reader registration
}
}
}, (result) => {
// Check console for debug output
if (result) {
console.log('Decoded:', result.codeResult.code);
}
});
```
## Development vs Production {#development-vs-production}
**Development builds** (`dist/quagga.js` or when using webpack dev server):
- Debug flags work
- Console output visible
- Larger file size
**Production builds** (`dist/quagga.min.js`):
- Debug flags are stripped out (no-op)
- No console output
- Smaller file size
To enable debug output in production:
1. Use the development build (`quagga.js` instead of `quagga.min.js`)
2. Set `ENV.development = true` before importing Quagga
## Summary {#summary}
Quagga2 provides **19 debug flags** organized into three categories:
**Console logging** (4 flags) - Minimal performance impact:
- `inputStream.debug.showImageDetails` - Frame grabber and image loading
- `decoder.debug.printReaderInfo` - Reader registration
- `locator.debug.showPatchSize` - Patch dimensions
- `locator.debug.showImageDetails` - Canvas initialization
**Visual canvas overlays** (13 flags) - Moderate to high performance impact:
- `decoder.debug.drawBoundingBox` - Barcode location box
- `decoder.debug.drawScanline` - Scan path
- `locator.debug.showCanvas` - Locator's internal canvas
- `locator.debug.showPatches` - All extracted patches
- `locator.debug.showFoundPatches` - Candidate patches
- `locator.debug.showSkeleton` - Skeleton structure
- `locator.debug.showLabels` - Component labels
- `locator.debug.showPatchLabels` - Patch labels
- `locator.debug.showRemainingPatchLabels` - Post-filter labels
- `locator.debug.boxFromPatches.showTransformed` - Transformed coordinates
- `locator.debug.boxFromPatches.showTransformedBox` - Transformed box
- `locator.debug.boxFromPatches.showBB` - Final bounding box
**Performance analysis** (2 flags) - High impact:
- `decoder.debug.showFrequency` - Signal frequency data
- `decoder.debug.showPattern` - Pattern extraction data
**Debugging strategy**: Start with console logging flags, then add visual overlays as needed. Disable all flags in production.
## Related {#related}
- [Configuration Reference](../reference/configuration.md) - Complete config documentation
- [Optimize Performance](optimize-performance.md) - Speed up barcode detection
- [Handle Difficult Barcodes](handle-difficult-barcodes.md) - Improve detection accuracy
## Changelog {#changelog}
**v1.8.4+** (November 2025): Debug flags introduced to replace always-on console spam
Prior versions logged debug information unconditionally, making it difficult to debug application code. The new flag system provides fine-grained control over diagnostic output.
---
**Questions?** Ask in [Gitter Chat](https://gitter.im/quaggaJS/Lobby) or [open an issue](https://github.com/ericblade/quagga2/issues).

View File

@@ -0,0 +1,254 @@
# Working with Box Coordinates {#working-with-box-coordinates}
This guide explains how Quagga2's coordinate system works and how to properly use `box`, `boxes`, and `line` coordinates for overlay rendering, especially when using `inputStream.size` to scale processing.
## Understanding the Coordinate System {#understanding-coordinate-system}
Quagga2 returns `box`, `boxes`, and `line` coordinates in **processed canvas coordinates**, not original image/video coordinates. This is important to understand when:
- Drawing overlay boxes on a video element
- Using `inputStream.size` to reduce processing resolution
- Cropping detected barcode regions from the original image
### Key Concepts {#key-concepts}
| Term | Description |
|------|-------------|
| **Real Size** | The actual dimensions of the source image/video |
| **Processed Size** | The scaled dimensions used for barcode detection (controlled by `inputStream.size`) |
| **Canvas Size** | The dimensions of the processing canvas (typically matches processed size) |
### How Coordinates are Generated {#how-coordinates-generated}
1. **Image Scaling**: When `inputStream.size` is set, the image is scaled so the longest side equals that value
2. **Localization**: Barcode regions are found in the scaled image
3. **Box Coordinates**: Returned coordinates are relative to the scaled/processed image
4. **halfSample Adjustment**: If `halfSample: true`, coordinates are automatically scaled 2x
## Converting Coordinates to Original Image Space {#converting-coordinates}
When you need to draw boxes on the original video/image (not the processed canvas), you must scale the coordinates.
### For Live Video Streams {#live-video-streams}
```javascript
Quagga.onDetected(function(result) {
if (!result.box) return;
// Get the video element
const video = document.querySelector('video');
const videoWidth = video.videoWidth; // Real video dimensions
const videoHeight = video.videoHeight;
// Get processed dimensions from Quagga
const canvas = Quagga.canvas.dom.image;
const processedWidth = canvas.width;
const processedHeight = canvas.height;
// Calculate scale factors
const scaleX = videoWidth / processedWidth;
const scaleY = videoHeight / processedHeight;
// Convert box coordinates to video space
const scaledBox = result.box.map(function(point) {
return [
point[0] * scaleX,
point[1] * scaleY
];
});
// Now use scaledBox for drawing on video overlay
drawBoxOnVideo(scaledBox);
});
```
### For Static Images with decodeSingle {#static-images-decodesingle}
```javascript
Quagga.decodeSingle({
src: './barcode.jpg',
inputStream: {
size: 800 // Process at 800px max dimension
},
// ... other config
}, function(result) {
if (!result || !result.box) return;
// Load original image to get real dimensions
const img = new Image();
img.onload = function() {
const realWidth = img.naturalWidth;
const realHeight = img.naturalHeight;
// Calculate what the processed size was
const aspectRatio = realWidth / realHeight;
let processedWidth, processedHeight;
if (aspectRatio > 1) {
// Landscape: width is the longest side
processedWidth = 800;
processedHeight = Math.floor(800 / aspectRatio);
} else {
// Portrait: height is the longest side
processedHeight = 800;
processedWidth = Math.floor(800 * aspectRatio);
}
// Calculate scale factors
const scaleX = realWidth / processedWidth;
const scaleY = realHeight / processedHeight;
// Convert coordinates
const scaledBox = result.box.map(function(point) {
return [
point[0] * scaleX,
point[1] * scaleY
];
});
// Use scaledBox for original image operations
cropBarcodeFromOriginal(scaledBox);
};
img.src = './barcode.jpg';
});
```
## Complete Example: Drawing Boxes on Live Video {#complete-example}
Here's a complete example showing how to draw accurate bounding boxes on a live video stream:
```javascript
// Initialize Quagga with reduced processing size for performance
Quagga.init({
inputStream: {
name: "Live",
type: "LiveStream",
target: document.querySelector('#scanner-container'),
constraints: {
width: 1280,
height: 720,
facingMode: "environment"
},
size: 640 // Process at 640px for better performance
},
locator: {
patchSize: "medium",
halfSample: true
},
decoder: {
readers: ["code_128_reader", "ean_reader"]
}
}, function(err) {
if (err) {
console.error(err);
return;
}
Quagga.start();
});
// Handle detections with coordinate scaling
Quagga.onDetected(function(result) {
const video = document.querySelector('video');
const overlay = document.querySelector('#overlay-canvas');
const ctx = overlay.getContext('2d');
// Match overlay to video size
overlay.width = video.videoWidth;
overlay.height = video.videoHeight;
// Get processed canvas size
const processedCanvas = Quagga.canvas.dom.image;
// Calculate scale factors
const scaleX = video.videoWidth / processedCanvas.width;
const scaleY = video.videoHeight / processedCanvas.height;
// Clear previous drawings
ctx.clearRect(0, 0, overlay.width, overlay.height);
// Draw all detected boxes
if (result.boxes) {
result.boxes.forEach(function(box) {
drawScaledBox(ctx, box, scaleX, scaleY, '#00ff00');
});
}
// Highlight the successfully decoded box
if (result.box) {
drawScaledBox(ctx, result.box, scaleX, scaleY, '#ff0000');
}
});
function drawScaledBox(ctx, box, scaleX, scaleY, color) {
ctx.strokeStyle = color;
ctx.lineWidth = 2;
ctx.beginPath();
// Scale and draw each point
const scaledPoints = box.map(p => [p[0] * scaleX, p[1] * scaleY]);
ctx.moveTo(scaledPoints[0][0], scaledPoints[0][1]);
for (let i = 1; i < scaledPoints.length; i++) {
ctx.lineTo(scaledPoints[i][0], scaledPoints[i][1]);
}
ctx.closePath();
ctx.stroke();
}
```
## When Coordinates Don't Need Scaling {#no-scaling-needed}
If you're drawing on Quagga's own overlay canvas (`Quagga.canvas.dom.overlay`), coordinates are already in the correct space:
```javascript
Quagga.onDetected(function(result) {
const ctx = Quagga.canvas.ctx.overlay;
const canvas = Quagga.canvas.dom.overlay;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// No scaling needed - coordinates match the overlay canvas
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, { x: 0, y: 1 }, ctx, {
color: "#00ff00",
lineWidth: 2
});
}
});
```
## Common Pitfalls {#common-pitfalls}
### 1. Forgetting halfSample Adjustment {#forgetting-halfsample}
If you're manually calculating processed size, remember that `halfSample: true` doesn't affect the returned coordinates (they're already adjusted).
### 2. Using Wrong Canvas Reference {#wrong-canvas-reference}
```javascript
// ❌ Wrong - using overlay canvas for scale calculation
const wrongWidth = Quagga.canvas.dom.overlay.width;
// ✅ Correct - using image canvas for scale calculation
const correctWidth = Quagga.canvas.dom.image.width;
```
### 3. Assuming Square Pixels {#assuming-square-pixels}
Always calculate scaleX and scaleY separately, as aspect ratios may differ:
```javascript
// ❌ Wrong - using single scale factor
const scale = videoWidth / canvasWidth;
// ✅ Correct - separate scale factors
const scaleX = videoWidth / canvasWidth;
const scaleY = videoHeight / canvasHeight;
```
## Performance Tips {#performance-tips}
1. **Use smaller `inputStream.size`** (e.g., 640-800) for live video to reduce CPU usage
2. **Cache scale factors** - recalculate only when video dimensions change
3. **Use requestAnimationFrame** for smooth overlay rendering
4. **Consider using Quagga's built-in overlay** when possible to avoid manual scaling

View File

@@ -0,0 +1,118 @@
# Quagga2 Documentation
Welcome to the Quagga2 documentation! Quagga2 is a JavaScript barcode scanner library supporting real-time location (localization) and decoding of various barcode types in both browser and Node.js environments.
![Badge](https://hitscounter.dev/api/hit?url=https%3A%2F%2Fericblade.github.io%2Fquagga2%2F&label=Views+%28Today%2FTotal%29&icon=link&color=%23198754&message=&style=plastic&tz=US%2FEastern)
[![Join the chat at https://gitter.im/quaggaJS/Lobby](https://badges.gitter.im/quaggaJS/Lobby.svg)](https://gitter.im/quaggaJS/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Quick Links
- [GitHub Repository](https://github.com/ericblade/quagga2)
- [Changelog](https://github.com/ericblade/quagga2/releases)
- [npm Package](https://www.npmjs.com/package/@ericblade/quagga2)
- [Live Examples](examples/)
## Getting Started
New to Quagga2? Start here:
- **[Installation & Quick Start](getting-started.md)** - Get up and running in minutes
---
## Documentation Structure
This documentation follows the [Divio Documentation System](https://documentation.divio.com/), organizing content into four types based on what you need:
### 📚 [Tutorials](tutorials/) - *Learning-oriented*
Step-by-step lessons to help you learn by doing. Perfect if you're just getting started.
- [Your First Barcode Scan](tutorials/first-scan.md) - Scan barcodes with your camera
- [Decoding Static Images](tutorials/static-image.md) - Process image files
- [Using with React](tutorials/react-integration.md) - React integration guide
- [Using with Node.js](tutorials/node-usage.md) - Server-side barcode scanning
### 🛠️ [How-To Guides](how-to-guides/) - *Task-oriented*
Practical guides for accomplishing specific goals. Use these when you have a problem to solve.
- [Configure Barcode Readers](how-to-guides/configure-readers.md) - Select which barcode types to scan
- [Optimize Performance](how-to-guides/optimize-performance.md) - Improve speed and accuracy
- [Handle Difficult Barcodes](how-to-guides/handle-difficult-barcodes.md) - Deal with poor lighting, rotation, etc.
- [Use Debug Flags](how-to-guides/use-debug-flags.md) - Enable diagnostic output
- [Create External Readers](how-to-guides/external-readers.md) - Build custom barcode decoder plugins
### 📖 [Reference](reference/) - *Information-oriented*
Technical descriptions and API documentation. Look here when you need precise details.
- [API Documentation](reference/api.md) - Complete API reference
- [Configuration Options](reference/configuration.md) - All config parameters explained
- [Camera Access API](reference/camera-access.md) - Camera control methods
- [Supported Barcode Types](reference/readers.md) - Available decoders
- [Browser Support](reference/browser-support.md) - Compatibility information
- [Dependencies](reference/dependencies.md) - Package dependencies explained
### 💡 [Explanation](explanation/) - *Understanding-oriented*
Background, context, and deeper understanding. Read these to learn *why* things work the way they do.
- [How Barcode Localization Works](explanation/how-barcode-localization-works.md) - The algorithm behind barcode detection
- [Algorithm Overview](explanation/algorithm-overview.md) - Image processing pipeline
- [Architecture](explanation/architecture.md) - Code structure and design decisions
---
## Additional Resources
- [Contributing Guide](contributing.md) - How to contribute to Quagga2
- [Gitter Chat](https://gitter.im/quaggaJS/Lobby) - Ask questions and get help
---
## Framework Integration
Using Quagga2 with a specific framework?
- **React**: [quagga2-react-example](https://github.com/ericblade/quagga2-react-example/) and [quagga2-redux-middleware](https://github.com/ericblade/quagga2-redux-middleware/)
- **Angular**: [ngx-barcode-scanner](https://github.com/julienboulay/ngx-barcode-scanner) or [mobile-scanning-demo](https://github.com/classycodeoss/mobile-scanning-demo)
- **Vue 2**: [vue-quagga-2](https://github.com/DevinNorgarb/vue-quagga-2)
- **Vue 3**: [vue3-quagga-2](https://github.com/nick-0101/vue3-quagga-2)
- **ThingWorx**: [ThingworxBarcodeScannerWidget](https://github.com/ptc-iot-sharing/ThingworxBarcodeScannerWidget)
---
## External Readers and Other Related Projects {#external-readers}
- **DataMatrix Reader**: [hackathi/quagga2-reader-datamatrix](https://github.com/hackathi/quagga2-reader-datamatrix)
- **QR Code Reader**: [ericblade/quagga2-qr-code-reader](https://github.com/ericblade/quagga2-qr-code-reader)
- **PDFBarcodeJS**: [rexshijaku/PDFBarcodeJS](https://github.com/rexshijaku/PDFBarcodeJS)
---
## Who Uses Quagga2? {#who-uses}
Quagga2 is used by developers worldwide in various applications, including inventory management, retail point-of-sale systems, library systems, and mobile scanning apps. If you're using Quagga2 in your project, let us know!
- **Internet Archive Open Library**: [internetarchive/openlibrary](https://github.com/internetarchive/openlibrary)
- **Grocy**: [grocy/grocy](https://github.com/grocy/grocy)
- **German Federal Agency For Technical Relief**: [mziech/thw-inventory](https://github.com/mziech/thw-inventory)
- **Food Coop Shop**: [foodcoopshop/foodcoopshop](https://github.com/foodcoopshop/foodcoopshop)
- **Rintagi**: [Rintagi/Low-Code-Development-Platform](https://github.com/Rintagi/Low-Code-Development-Platform)
- **LINE**: [line/line-api-use-case-smart-retail](https://github.com/line/line-api-use-case-smart-retail)
- **Veganify**: [frontendnetwork/veganify](https://github.com/frontendnetwork/veganify)
- **Appsemble**: [Appsemble](https://gitlab.com/appsemble/appsemble)
- **Intake24**: [intake24/intake24](https://github.com/intake24/intake24)
- **FridgeToPlate**: [COS301-SE-2023/FridgeToPlate](https://github.com/COS301-SE-2023/FridgeToPlate)
- **Nutri-Scanner**: [kishan9535/Nutri-Scanner](https://github.com/kishan9535/Nutri-Scanner)
- **Musclog**: [blopa/musclog-app](https://github.com/blopa/musclog-app)
---
## About This Documentation
This documentation is maintained alongside the Quagga2 codebase. If you find errors or have suggestions for improvement, please [open an issue](https://github.com/ericblade/quagga2/issues) or submit a pull request.
**Last Updated**: November 2025

View File

@@ -0,0 +1,758 @@
# API Documentation {#api-documentation}
Complete reference for all Quagga2 methods, callbacks, and events.
## Core Methods {#core-methods}
### `Quagga.init(config, callback)` {#quagga-init}
Initializes the library with the given configuration and requests camera access if using live stream mode.
**Parameters**:
- `config` (Object) - Configuration object. See [Configuration Reference](configuration.md) for complete details.
- `callback` (Function) - Called when initialization completes: `callback(err)`
- `err` (Error | null) - Error object if initialization failed, `null` on success
**Returns**: `void`
**Example - Live Camera**:
```javascript
Quagga.init({
inputStream: {
type: "LiveStream",
target: document.querySelector('#scanner') // Or '#scanner'
},
decoder: {
readers: ["code_128_reader"]
}
}, function(err) {
if (err) {
console.error("Initialization failed:", err);
return;
}
console.log("Initialization successful, ready to start");
Quagga.start();
});
```
**Example - Static Images**:
```javascript
Quagga.init({
inputStream: {
type: "ImageStream",
src: "/path/to/images/*.jpg",
target: document.querySelector('#scanner')
},
decoder: {
readers: ["ean_reader", "code_128_reader"]
}
}, function(err) {
if (err) {
console.error(err);
return;
}
Quagga.start();
});
```
**Error Handling**:
The callback receives an `err` parameter if initialization fails. Common causes:
- User denies camera permission
- No camera device found
- Browser doesn't support required APIs
- Invalid configuration parameters
Always check for errors before calling `start()`:
**Target Element**:
If no `target` is specified, Quagga looks for an element matching the CSS selector `#interactive.viewport` (for backwards compatibility).
### `Quagga.start()` {#quagga-start}
Starts the video stream and begins locating and decoding barcodes.
**Parameters**: None
**Returns**: `void`
**Example**:
```javascript
Quagga.init(config, function(err) {
if (!err) {
Quagga.start();
}
});
```
**Prerequisites**:
- `Quagga.init()` must have completed successfully
- For live stream: Camera permission must be granted
**Note**: Call this in the `init()` callback after checking for errors.
### `Quagga.stop()` {#quagga-stop}
Stops the decoder from processing images and disconnects the camera if one was requested.
**Parameters**: None
**Returns**: `Promise<void>` - Resolves when cleanup is complete
**Example**:
```javascript
// Stop scanning
await Quagga.stop();
console.log("Scanner stopped");
// Or with .then()
Quagga.stop().then(() => {
console.log("Scanner stopped");
});
```
**Behavior**:
- Stops processing new frames
- If using live camera: disconnects and releases camera
- Does not remove event listeners (use `offDetected()` / `offProcessed()` for that)
- Returns a Promise that resolves when camera release is complete
### `Quagga.pause()` {#quagga-pause}
Pauses frame processing without stopping the camera or releasing resources.
**Parameters**: None
**Returns**: `void`
**Example**:
```javascript
// Pause scanning temporarily
Quagga.pause();
// Later, resume scanning
Quagga.start();
```
**Behavior**:
- Stops processing new frames (no more `onProcessed` or `onDetected` callbacks)
- **Does not stop the camera** - the video stream continues running
- **Does not release resources** - the camera remains connected
- Can be resumed by calling `Quagga.start()`
**Use Cases**:
- Temporarily pause scanning while showing a modal or dialog
- Reduce CPU usage when the scanner is not visible
- Pause after detecting a barcode to allow user confirmation before resuming
**Difference from `stop()`**:
| Aspect | `pause()` | `stop()` |
|--------|-----------|----------|
| Frame processing | Stops | Stops |
| Camera stream | **Continues** | Disconnects |
| Resources | **Retained** | Released |
| Resume with | `start()` | `init()` + `start()` |
> **Note**: Since `pause()` keeps the camera running, the user's camera indicator light will remain on. If you want to fully release the camera, use `stop()` instead.
### `Quagga.onDetected(callback)` {#quagga-ondetected}
Registers a callback that is triggered when a barcode is successfully located and decoded.
**Parameters**:
- `callback` (Function) - Handler function: `callback(data)`
- `data` (Object) - Result object containing decoded barcode information
**Returns**: `void`
**Example**:
```javascript
Quagga.onDetected(function(result) {
const code = result.codeResult.code;
const format = result.codeResult.format;
console.log(`Detected ${format} barcode: ${code}`);
// Process the barcode
processBarcode(code);
});
```
**Multiple Handlers**:
You can register multiple handlers - all will be called:
```javascript
Quagga.onDetected(handler1);
Quagga.onDetected(handler2); // Both execute on detection
```
### `Quagga.onProcessed(callback)` {#quagga-onprocessed}
Registers a callback that is called for each processed frame, regardless of detection success.
**Parameters**:
- `callback` (Function) - Handler function: `callback(data)`
- `data` (Object) - Processing result with detailed information
**Returns**: `void`
**Example**:
```javascript
Quagga.onProcessed(function(result) {
const drawingCtx = Quagga.canvas.ctx.overlay;
const drawingCanvas = Quagga.canvas.dom.overlay;
if (result) {
// Draw boxes and lines for visualization
if (result.boxes) {
drawingCtx.clearRect(0, 0,
drawingCanvas.width, drawingCanvas.height);
result.boxes.forEach(function(box) {
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1},
drawingCtx, {color: "blue", lineWidth: 2});
});
}
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1},
drawingCtx, {color: "green", lineWidth: 2});
}
if (result.codeResult && result.codeResult.code) {
// Successfully decoded
console.log("Code detected:", result.codeResult.code);
}
}
});
```
**Use Cases**:
- Custom visualization of detection process
- Counting processed frames
- Performance monitoring
- Drawing custom overlays
### `Quagga.drawScannerArea()` {#quagga-drawscannerarea}
Manually draws the scanner area overlay on the overlay canvas using the configured `inputStream.area`.
**Parameters**: None (uses the area configuration from `Quagga.init()`)
**Returns**: `void`
**Behavior**:
- Only draws when `locate` is `false` and `inputStream.area` is configured with styling
- Uses the actual adjusted scanning area (after patch alignment) to accurately match where barcodes will be detected
- Automatically accounts for the area offset and dimensions
- Skips drawing if no styling is provided (no `borderColor`, `borderWidth`, or `backgroundColor`)
- Requires `canvas.createOverlay: true` so the overlay canvas exists
- Drawing occurs automatically every processed frame when conditions are met
**Example**:
```javascript
// Configure area during init
Quagga.init({
inputStream: {
area: {
top: "30%",
right: "10%",
bottom: "30%",
left: "10%",
borderColor: "#0F0",
borderWidth: 2,
backgroundColor: "rgba(255,0,0,0.15)"
}
},
locate: false
});
// Later, manually redraw if you've cleared the overlay
Quagga.drawScannerArea();
```
### `Quagga.offDetected(handler)` {#quagga-offdetected}
Removes a previously registered `onDetected` handler.
**Parameters**:
- `handler` (Function, optional) - Specific handler to remove. If omitted, **all** handlers are removed.
**Returns**: `void`
**Example**:
```javascript
function myHandler(result) {
console.log(result.codeResult.code);
}
Quagga.onDetected(myHandler);
// Later: remove specific handler
Quagga.offDetected(myHandler);
// Or remove all handlers
Quagga.offDetected();
```
### `Quagga.offProcessed(handler)` {#quagga-offprocessed}
Removes a previously registered `onProcessed` handler.
**Parameters**:
- `handler` (Function, optional) - Specific handler to remove. If omitted, **all** handlers are removed.
**Returns**: `void`
**Example**:
```javascript
function processHandler(result) {
// Process frame
}
Quagga.onProcessed(processHandler);
// Remove specific handler
Quagga.offProcessed(processHandler);
// Or remove all handlers
Quagga.offProcessed();
```
### `Quagga.decodeSingle(config, callback)` {#quagga-decodesingle}
Decodes a single image without using `getUserMedia`. Useful for processing uploaded images or static images.
**Parameters**:
- `config` (Object) - Configuration object (subset of full config)
- `callback` (Function) - Result handler: `callback(result)`
- `result` (Object) - Same format as `onDetected` callback
**Returns**: `void`
**Important - Default Scaling**: `decodeSingle` has a built-in default of `inputStream.size: 800`. This means images are **automatically scaled to 800px** on their longest side (both larger images scaled down AND smaller images scaled up). The result's `box`, `boxes`, and `line` coordinates are returned in this scaled coordinate space, not the original image dimensions. To use the original image dimensions without scaling, set `inputStream.size` to `0`.
**Example**:
```javascript
Quagga.decodeSingle({
src: '/images/barcode.jpg', // Or data URL
decoder: {
readers: ["code_128_reader", "ean_reader"]
},
locate: true // Try to locate barcode in image
// Note: inputStream.size defaults to 800; images are scaled to 800px (up or down)
}, function(result) {
if (result && result.codeResult) {
console.log("Detected:", result.codeResult.code);
console.log("Format:", result.codeResult.format);
} else {
console.log("No barcode detected");
}
});
```
**Using Data URLs**:
```javascript
// From file input
document.querySelector('#file-input').addEventListener('change', function(e) {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = function(event) {
Quagga.decodeSingle({
src: event.target.result, // Data URL
decoder: {
readers: ["code_128_reader"]
}
// Default size: 800 applies - image scaled if larger
}, function(result) {
if (result && result.codeResult) {
alert("Barcode: " + result.codeResult.code);
}
});
};
reader.readAsDataURL(file);
});
```
**Node.js Usage**:
```javascript
const Quagga = require('@ericblade/quagga2').default;
Quagga.decodeSingle({
src: "./barcode.jpg",
inputStream: {
size: 800 // This is actually the default; shown explicitly here
},
decoder: {
readers: ["code_128_reader"]
}
}, function(result) {
if (result && result.codeResult) {
console.log("Code:", result.codeResult.code);
}
});
```
## Result Object {#result-object}
The result object passed to `onDetected`, `onProcessed`, and `decodeSingle` callbacks contains detailed information about the detection and decoding process.
### Complete Result Structure {#result-structure}
```javascript
{
codeResult: {
code: "FANAVF1461710", // The decoded barcode string
format: "code_128", // Barcode format
start: 355, // Start position
end: 26, // End position
codeset: 100, // Code 128 specific
startInfo: {
error: 1.0,
code: 104,
start: 21,
end: 41
},
decodedCodes: [ // Individual code segments
{ code: 104, start: 21, end: 41 },
// ... more segments
{ error: 0.88, code: 106, start: 328, end: 350 }
],
endInfo: {
error: 0.88,
code: 106,
start: 328,
end: 350
},
direction: -1 // Scan direction
},
line: [ // Scan line coordinates
{ x: 25.97, y: 360.56 },
{ x: 401.92, y: 70.88 }
],
angle: -0.657, // Rotation angle in radians
pattern: [0, 0, 1, 1, ...], // Bar pattern (0=space, 1=bar)
box: [ // Primary bounding box (4 corners)
[77.41, 410.93], // Top-left
[0.05, 310.54], // Top-right
[360.16, 33.06], // Bottom-right
[437.51, 133.45] // Bottom-left
],
boxes: [ // All detected boxes
[/* box 1 */],
[/* box 2 */],
// ...
]
}
```
### Result Properties {#result-properties}
| Property | Type | Description |
|----------|------|-------------|
| `codeResult` | Object | Decoded barcode information (may be `undefined` if detection failed) |
| `codeResult.code` | String | The decoded barcode value |
| `codeResult.format` | String | Barcode format (e.g., "code_128", "ean_13") |
| `codeResult.start` | Number | Start position in pattern |
| `codeResult.end` | Number | End position in pattern |
| `codeResult.direction` | Number | Scan direction (1 or -1) |
| `codeResult.supplement` | Object | (Optional) Supplement barcode data for EAN-13/UPC-A with EAN-2 or EAN-5 extensions |
| `codeResult.supplement.code` | String | The decoded supplement value (2 or 5 digits) |
| `codeResult.supplement.format` | String | Supplement format: "ean_2" or "ean_5" |
| `line` | Array | Two points defining the scan line |
| `angle` | Number | Barcode rotation angle (radians) |
| `pattern` | Array | Binary pattern (0=space, 1=bar) |
| `box` | Array | Bounding box coordinates (4 corner points) |
| `boxes` | Array | All candidate boxes found during localization. When `locate` is `false`, this contains a single box representing the actual adjusted scanning area (after patch alignment) |
> **Note: `boxes` with `locate: false`**
>
> When `locate` is `false` and an `inputStream.area` is configured, `result.boxes` contains a single box representing the actual scanning area dimensions. This box reflects the adjusted dimensions after patch alignment (which can differ slightly from the percentage-based area due to rounding to patch size multiples). Use these coordinates if you need to know the exact scanning rectangle:
>
> ```javascript
> Quagga.onProcessed(function(result) {
> if (result.boxes && result.boxes.length > 0) {
> // When locate=false, boxes[0] is the actual scanning area
> const scanArea = result.boxes[0];
> console.log("Scanning area corners:", scanArea);
> }
> });
> ```
> **Important: Coordinate System**
>
> The `box`, `boxes`, and `line` coordinates are returned in **processed canvas coordinates**, not original image/video coordinates. If you're using `inputStream.size` to scale the processing resolution (e.g., for performance), you'll need to scale these coordinates to match your original video/image dimensions.
>
> ```javascript
> // Scale coordinates to original video size
> const scaleX = video.videoWidth / Quagga.canvas.dom.image.width;
> const scaleY = video.videoHeight / Quagga.canvas.dom.image.height;
> const scaledBox = result.box.map(p => [p[0] * scaleX, p[1] * scaleY]);
> ```
>
> See [Working with Box Coordinates](../how-to-guides/working-with-coordinates.md) for complete examples.
### Checking for Successful Detection {#checking-detection}
```javascript
Quagga.onDetected(function(result) {
// Always check if codeResult exists
if (result && result.codeResult && result.codeResult.code) {
console.log("Detected:", result.codeResult.code);
}
});
```
### Using Multiple Barcode Detection {#multiple-barcode-detection}
When `decoder.multiple` is `true`, results are returned as an array:
```javascript
Quagga.init({
decoder: {
readers: ["code_128_reader"],
multiple: true
}
});
Quagga.onDetected(function(result) {
// result is an array of result objects
result.forEach(function(item) {
if (item.codeResult) {
console.log("Code:", item.codeResult.code);
console.log("Box:", item.box);
}
});
});
```
## Canvas Access {#canvas-access}
Quagga automatically creates and manages two canvas elements for visualization. These are positioned over the video/image stream and sized to match the processing dimensions.
### Canvas Structure {#canvas-structure}
```javascript
Quagga.canvas = {
dom: {
image: HTMLCanvasElement, // Canvas for processed image data
overlay: HTMLCanvasElement | null // Transparent canvas for drawing overlays
},
ctx: {
image: CanvasRenderingContext2D, // Context for image canvas
overlay: CanvasRenderingContext2D | null // Context for overlay canvas
}
};
```
> **Note**: The overlay canvas can be `null` if `canvas.createOverlay` is set to `false` in the configuration. See [Canvas Configuration](configuration.md#canvas-configuration) for details.
### Overlay Canvas {#overlay-canvas}
The **overlay canvas** (`Quagga.canvas.dom.overlay`) is a transparent canvas element positioned over the video stream. It's automatically created when Quagga initializes (unless `canvas.createOverlay` is `false`) and is designed for drawing bounding boxes, scan lines, and other visual feedback.
**Key characteristics:**
- Has CSS class `drawingBuffer`
- Sized to match the processed image dimensions (`inputStream.size`)
- Positioned absolutely over the video/image element
- Automatically appended to the viewport container
- Coordinates match the processed image space (no scaling needed)
- Can be disabled via `canvas.createOverlay: false` for performance
**Accessing the overlay:**
```javascript
const overlay = Quagga.canvas.dom.overlay;
const overlayCtx = Quagga.canvas.ctx.overlay;
// Always check if overlay exists before using
if (overlayCtx && overlay) {
// Clear overlay
overlayCtx.clearRect(0, 0, overlay.width, overlay.height);
// Draw custom shapes
overlayCtx.strokeStyle = "red";
overlayCtx.lineWidth = 3;
overlayCtx.strokeRect(10, 10, 100, 100);
}
```
### Image Canvas {#image-canvas}
The **image canvas** (`Quagga.canvas.dom.image`) contains the processed grayscale image data used for barcode detection. This is primarily for internal use and debugging.
**Key characteristics:**
- Has CSS class `imgBuffer`
- Contains the grayscale/processed image data
- Useful for debugging locator issues
### When to Use Each Canvas {#when-to-use-canvas}
| Use Case | Canvas to Use |
|----------|---------------|
| Drawing bounding boxes | `overlay` |
| Highlighting detected barcodes | `overlay` |
| Custom scan line visualization | `overlay` |
| Debugging image processing | `image` |
| Checking processed resolution | Either (they have same dimensions) |
### Important: Coordinate System {#canvas-coordinate-system}
When drawing on the overlay canvas, use `result.box` and `result.boxes` coordinates directly - **no scaling is needed**. These coordinates are already in the overlay canvas's coordinate space.
```javascript
Quagga.onProcessed(function(result) {
const ctx = Quagga.canvas.ctx.overlay;
const canvas = Quagga.canvas.dom.overlay;
// Clear previous drawings
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw box directly - coordinates already match the overlay canvas
if (result && result.box) {
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, ctx, {
color: "green", lineWidth: 2
});
}
});
```
> **Note**: Scaling is only needed when drawing on a **different** canvas (like a custom overlay on the original video element). See [Working with Box Coordinates](../how-to-guides/working-with-coordinates.md) for details.
### CSS Styling {#canvas-css-styling}
The overlay canvas can be styled with CSS for positioning:
```css
/* Default positioning (handled automatically by Quagga) */
canvas.drawingBuffer {
position: absolute;
top: 0;
left: 0;
}
/* Ensure proper stacking */
#scanner-container {
position: relative;
}
```
## ImageDebug Helper {#imagedebug-helper}
Quagga provides a helper for drawing debug visualizations:
```javascript
// Draw a path (array of points)
Quagga.ImageDebug.drawPath(points, offset, ctx, options);
// Example
Quagga.ImageDebug.drawPath(
result.box,
{ x: 0, y: 1 },
overlayCtx,
{ color: "green", lineWidth: 2 }
);
```
## Complete Example {#complete-example}
```javascript
// Initialize
Quagga.init({
inputStream: {
type: "LiveStream",
target: document.querySelector('#scanner'),
constraints: {
width: 640,
height: 480,
facingMode: "environment"
}
},
decoder: {
readers: ["code_128_reader", "ean_reader"]
}
}, function(err) {
if (err) {
console.error(err);
return;
}
// Start scanning
Quagga.start();
});
// Handle detections
Quagga.onDetected(function(result) {
console.log("Barcode detected:", result.codeResult.code);
// Stop after first detection
Quagga.stop();
// Cleanup
Quagga.offDetected();
Quagga.offProcessed();
});
// Visualize processing
Quagga.onProcessed(function(result) {
const ctx = Quagga.canvas.ctx.overlay;
const canvas = Quagga.canvas.dom.overlay;
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (result && result.box) {
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, ctx, {
color: "green",
lineWidth: 2
});
}
});
// Stop button
document.querySelector('#stop').addEventListener('click', function() {
Quagga.stop();
Quagga.offDetected();
Quagga.offProcessed();
});
```
## Related {#related}
- [Configuration Reference](configuration.md) - Complete configuration options
- [CameraAccess API](camera-access.md) - Camera control methods
- [Supported Barcode Types](readers.md) - Available barcode readers
- [Getting Started](../getting-started.md) - Basic usage examples
---
[← Back to Reference](index.md)

View File

@@ -0,0 +1,109 @@
# Browser Support {#browser-support}
Quagga2 makes use of many modern Web APIs which are not implemented by all browsers yet. This page details browser compatibility and required APIs.
## Operating Modes {#operating-modes}
Quagga2 operates in two modes:
1. **Analyzing static images** - Process existing image files
2. **Using a camera** - Decode images from a live video stream
The latter requires the MediaDevices API for camera access.
## Browser Compatibility {#browser-compatibility}
You can track the compatibility of the used Web APIs for each mode:
- [Static Images](http://caniuse.com/#feat=canvas,typedarrays,bloburls,blobbuilder)
- [Live Stream](http://caniuse.com/#feat=canvas,typedarrays,bloburls,blobbuilder,stream)
### Static Image Mode {#static-image-mode}
The following APIs must be supported by your browser:
- [Canvas](http://caniuse.com/#feat=canvas)
- [Typed Arrays](http://caniuse.com/#feat=typedarrays)
- [Blob URLs](http://caniuse.com/#feat=bloburls)
- [Blob Builder](http://caniuse.com/#feat=blobbuilder)
### Live Stream Mode {#live-stream-mode}
In addition to the APIs required for static images:
- [MediaDevices API](http://caniuse.com/#feat=stream) - Required for camera access
## Secure Origins Required {#secure-origins}
**Important**: Accessing `getUserMedia` requires a secure origin in most browsers:
- `http://` can **only** be used on `localhost`
- All other hostnames **must** be served via `https://`
This is a browser security requirement. Read more in the [Chrome M47 WebRTC Release Notes](https://groups.google.com/forum/#!topic/discuss-webrtc/sq5CVmY69sc).
## Feature Detection {#feature-detection}
### Detecting getUserMedia Support {#detecting-getusermedia}
Every browser implements the `mediaDevices.getUserMedia` API differently. It's highly recommended to include [webrtc-adapter](https://github.com/webrtc/adapter) in your project for cross-browser compatibility.
**How to test browser capabilities:**
```javascript
if (navigator.mediaDevices && typeof navigator.mediaDevices.getUserMedia === 'function') {
// Safe to use getUserMedia
console.log('Camera access is supported');
} else {
// Camera access not available
console.log('Camera access is NOT supported');
}
```
### Browser Support Table {#browser-support-table}
The above condition evaluates as follows:
| Browser | Result | Notes |
|---------------|---------|-------|
| Chrome | `true` | Full support |
| Firefox | `true` | Full support |
| Edge | `true` | Full support |
| Safari iOS | `true` | Requires HTTPS |
| IE 11 | `false` | Not supported |
| Safari Desktop| `true` | macOS 11+ |
## Known Issues {#known-issues}
### iOS Torch/Flash {#ios-torch-flash}
Torch (flash) control via `CameraAccess.enableTorch()` and `CameraAccess.disableTorch()` does **not work** on iOS devices running version 16.4 and earlier. Support on later versions may vary.
### Safari Limitations {#safari-limitations}
- Older Safari versions may require user interaction before camera access
- Some older iOS versions have limited WebRTC support
### Internet Explorer {#internet-explorer}
Internet Explorer 11 and below do not support the MediaDevices API and cannot use live camera features. Static image decoding may work with polyfills, but this is not officially supported.
## Recommendations {#recommendations}
For best compatibility:
1. **Use HTTPS** - Required for camera access on all non-localhost domains
2. **Include webrtc-adapter** - Normalizes browser differences
3. **Feature detect** - Check for API support before attempting to use camera
4. **Provide fallbacks** - Offer file upload as alternative to camera access
5. **Test thoroughly** - Browser behavior varies, especially on mobile
## Related {#related}
- [Configuration Reference](configuration.md) - How to configure Quagga2
- [Camera Access API](camera-access.md) - Camera control methods
- [Getting Started](../getting-started.md) - Installation and setup
---
[← Back to Reference](index.md)

View File

@@ -0,0 +1,376 @@
# CameraAccess API {#cameraaccess-api}
Quagga2 exposes a `CameraAccess` API for direct control of camera functionality. This API provides shortcuts for commonly used camera operations.
**Access**: `Quagga.CameraAccess`
## Overview {#overview}
The CameraAccess API allows you to:
- Request and release camera access
- Enumerate available video devices
- Control camera torch (flash)
- Get information about active video streams and tracks
All methods return Promises for async operation handling.
## Methods {#methods}
### `CameraAccess.request(videoElement, constraints)` {#cameraaccess-request}
Initializes the camera and starts playback.
**Parameters**:
- `videoElement` (HTMLVideoElement | null) - Video element to display camera stream. If `null`, camera initializes but remains invisible.
- `constraints` (MediaTrackConstraints, optional) - Camera selection and configuration constraints.
**Returns**: `Promise<void>` - Resolves when camera is ready, rejects on error.
**Example**:
```javascript
const video = document.querySelector('#camera-video');
// Request camera with default constraints
await Quagga.CameraAccess.request(video);
// Request specific camera
await Quagga.CameraAccess.request(video, {
facingMode: 'environment', // Back camera on mobile
width: { ideal: 1280 },
height: { ideal: 720 }
});
// Request camera by device ID
const deviceId = 'abc123...';
await Quagga.CameraAccess.request(video, { deviceId });
// Initialize camera without displaying (for probing)
await Quagga.CameraAccess.request(null);
```
**Use cases**:
- Start camera before Quagga initialization
- Probe camera availability and permissions
- Initialize camera without displaying video
### `CameraAccess.release()` {#cameraaccess-release}
Stops the video stream and releases all camera resources.
**Returns**: `Promise<void>` - Resolves when all tracks are stopped and resources released.
**Example**:
```javascript
// Stop camera
await Quagga.CameraAccess.release();
console.log('Camera released');
```
**Behavior**:
1. Pauses the video element
2. Stops all tracks in the media stream
3. Releases camera for use by other applications
**Note**: Always call `release()` when finished with the camera to free system resources.
### `CameraAccess.enumerateVideoDevices(constraints?)` {#cameraaccess-enumeratevideodevices}
Lists all available video input devices (cameras), optionally filtered by constraints.
**Parameters**:
- `constraints` (MediaTrackConstraints, optional) - Constraints to filter devices. When provided, only devices that can satisfy the given constraints will be returned.
**Returns**: `Promise<MediaDeviceInfo[]>` - Array of video device information.
**Example**:
```javascript
// Get all video devices
const devices = await Quagga.CameraAccess.enumerateVideoDevices();
devices.forEach(device => {
console.log('Device:', device.label);
console.log('Device ID:', device.deviceId);
console.log('Group ID:', device.groupId);
});
// Example output:
// Device: Front Camera
// Device ID: abc123...
// Device: Back Camera
// Device ID: def456...
```
**Filtering devices with constraints**:
```javascript
// Get only devices that support a minimum resolution
const hdDevices = await Quagga.CameraAccess.enumerateVideoDevices({
width: { min: 1280 },
height: { min: 720 }
});
// Get only back-facing cameras
const backCameras = await Quagga.CameraAccess.enumerateVideoDevices({
facingMode: 'environment'
});
// Eliminate wide-angle only cameras by specifying aspect ratio
const standardCameras = await Quagga.CameraAccess.enumerateVideoDevices({
aspectRatio: { ideal: 1.777 } // 16:9
});
```
**Use cases**:
- Build camera selector UI
- Detect available cameras before initialization
- Check for front/back camera availability on mobile
- Filter out cameras that don't meet quality requirements
- Eliminate wide-angle cameras that may not be suitable for barcode scanning
**Note**: Device labels may be empty strings until camera permission is granted. When using constraints, the method will request temporary access to each device to test if it satisfies the constraints.
### `CameraAccess.getActiveStreamLabel()` {#cameraaccess-getactivestreamlabel}
Gets the label of the currently active video track.
**Returns**: `string` - Label of active video track (e.g., "Back Camera", "USB Camera").
**Example**:
```javascript
const label = Quagga.CameraAccess.getActiveStreamLabel();
console.log('Using camera:', label);
// Output: "Using camera: Back Camera"
```
**Use cases**:
- Display which camera is currently active
- Verify correct camera is being used
- Logging and debugging
### `CameraAccess.getActiveStream()` {#cameraaccess-getactivestream}
Gets the complete MediaStream object for the currently active video.
**Returns**: `MediaStream | null` - The active MediaStream object, or `null` if no camera is active.
**Example**:
```javascript
const stream = Quagga.CameraAccess.getActiveStream();
if (stream) {
console.log('Stream ID:', stream.id);
console.log('Stream active:', stream.active);
console.log('Video tracks:', stream.getVideoTracks().length);
console.log('Audio tracks:', stream.getAudioTracks().length);
// Clone the stream
const clonedStream = stream.clone();
}
// Pass stream to WebRTC peer connection
if (stream?.active) {
peerConnection.addStream(stream);
}
```
**Use cases**:
- Pass the stream to WebRTC peer connections
- Clone the stream for multiple consumers
- Check if the stream is still active via `stream.active`
- Access the stream ID
- Work with all tracks (video and audio) in the stream
**Note**: For accessing just the video track, use `getActiveTrack()` instead.
### `CameraAccess.getActiveTrack()` {#cameraaccess-getactivetrack}
Gets the MediaStreamTrack for the currently active video.
**Returns**: `MediaStreamTrack | null` - Active video track object, or `null` if no camera is active.
**Example**:
```javascript
const track = Quagga.CameraAccess.getActiveTrack();
console.log('Track state:', track.readyState);
console.log('Track settings:', track.getSettings());
console.log('Track capabilities:', track.getCapabilities());
// Get current resolution
const settings = track.getSettings();
console.log(`Resolution: ${settings.width}x${settings.height}`);
```
**Use cases**:
- Access advanced track capabilities
- Monitor track state
- Apply additional constraints
- Access camera capabilities (zoom, focus, etc.)
### `CameraAccess.enableTorch()` {#cameraaccess-enabletorch}
Turns on the camera torch (flash).
**Returns**: `Promise<void>` - Resolves when torch is enabled, rejects on error.
**Example**:
```javascript
try {
await Quagga.CameraAccess.enableTorch();
console.log('Torch enabled');
} catch (error) {
console.error('Failed to enable torch:', error);
}
```
**Browser Support**:
- ✅ Chrome (Android)
- ✅ Chrome (Desktop with supported cameras)
- ❌ Safari iOS 16.4 and earlier
- ⚠️ Safari iOS later versions - may or may not work
**Requirements**:
- Camera must support torch capability
- Camera must be actively streaming
- Browser must support torch constraint
**Note**: Always wrap in try-catch as not all devices support torch.
### `CameraAccess.disableTorch()` {#cameraaccess-disabletorch}
Turns off the camera torch (flash).
**Returns**: `Promise<void>` - Resolves when torch is disabled, rejects on error.
**Example**:
```javascript
try {
await Quagga.CameraAccess.disableTorch();
console.log('Torch disabled');
} catch (error) {
console.error('Failed to disable torch:', error);
}
```
**Browser Support**: Same as `enableTorch()`.
## Complete Example {#complete-example}
```javascript
// Enumerate cameras and let user choose
const devices = await Quagga.CameraAccess.enumerateVideoDevices();
const backCamera = devices.find(d => d.label.includes('back'));
// Initialize camera
const video = document.querySelector('#video');
await Quagga.CameraAccess.request(video, {
deviceId: backCamera.deviceId
});
console.log('Active camera:', Quagga.CameraAccess.getActiveStreamLabel());
// Enable torch for better scanning in dark environments
try {
await Quagga.CameraAccess.enableTorch();
} catch (error) {
console.log('Torch not available');
}
// ... use camera for scanning ...
// Cleanup
await Quagga.CameraAccess.disableTorch();
await Quagga.CameraAccess.release();
```
## Torch Control in Live Scanning {#torch-control}
For torch control during live scanning, you may want to provide a toggle button:
```javascript
let torchEnabled = false;
document.querySelector('#torch-toggle').addEventListener('click', async () => {
try {
if (torchEnabled) {
await Quagga.CameraAccess.disableTorch();
torchEnabled = false;
} else {
await Quagga.CameraAccess.enableTorch();
torchEnabled = true;
}
} catch (error) {
console.error('Torch control failed:', error);
alert('Torch not available on this device');
}
});
```
## Advanced Camera Control {#advanced-camera-control}
For advanced camera control (zoom, focus, etc.), use the MediaStreamTrack API:
```javascript
const track = Quagga.CameraAccess.getActiveTrack();
const capabilities = track.getCapabilities();
// Check if zoom is supported
if (capabilities.zoom) {
console.log('Zoom range:', capabilities.zoom.min, '-', capabilities.zoom.max);
// Apply zoom
await track.applyConstraints({
advanced: [{ zoom: 2.0 }]
});
}
```
Read more: [MediaStreamTrack Capabilities](https://www.oberhofer.co/mediastreamtrack-and-its-capabilities)
## Error Handling {#error-handling}
Always handle errors when using CameraAccess methods:
```javascript
try {
await Quagga.CameraAccess.request(video);
} catch (error) {
if (error.name === 'NotAllowedError') {
console.error('Camera permission denied');
} else if (error.name === 'NotFoundError') {
console.error('No camera found');
} else {
console.error('Camera error:', error);
}
}
```
## Related {#related}
- [Browser Support](browser-support.md) - Camera compatibility information
- [Configuration Reference](configuration.md) - Camera configuration in Quagga.init()
- [API Documentation](api.md) - Main Quagga API methods
- [Tips & Tricks](../how-to-guides/tips-and-tricks.md) - Camera optimization tips
---
[← Back to Reference](index.md)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,295 @@
# Quagga2 Dependencies
This document explains the dependency structure of Quagga2 and clarifies which packages are runtime code dependencies versus build/test tools.
## Background
Quagga2 bundles all its code with Webpack, producing standalone browser and Node.js builds. As a result, **all packages are listed as `devDependencies`** in `package.json` because consumers never directly install them - they only use the pre-built bundles in `dist/` and `lib/`.
However, this makes it unclear which packages are actual code dependencies (bundled into the final output) versus which are just build/test tools. This document clarifies that distinction.
---
## Runtime Code Dependencies
These packages contain code that is **imported by the source code** and **bundled into the final output**:
### Core Libraries
- **`gl-matrix`** (^3.4.4)
- **Purpose**: High-performance vector and matrix math operations
- **Usage**: Used throughout the codebase for geometric calculations
- **Files**:
- `src/quagga/quagga.ts` - vec2 operations for bounding boxes
- `src/quagga/initBuffers.ts` - vec2 for buffer initialization
- `src/locator/barcode_locator.js` - vec2, mat2 for barcode location
- `src/common/image_wrapper.ts` - vec2 for image transforms
- `src/common/cvutils/ImageRef.ts` - vec2, vec3 for computer vision
- `src/common/cluster.js` - vec2 for clustering algorithms
- **`lodash`** (^4.17.21)
- **Purpose**: Utility functions for object manipulation
- **Usage**: Primarily `merge()` for config merging, `pick()` for object selection
- **Files**:
- `src/quagga.js` - merge() for configuration
- `src/QuaggaStatic.ts` - merge() for configuration
- `src/reader/ean_reader.ts` - merge() for config defaults
- `src/reader/i2of5_reader.ts` - merge() for config defaults
- `src/input/camera_access.ts` - pick() for MediaTrackConstraints
- `src/locator/test/barcode_locator.spec.ts` - merge() in tests
### Image Processing
- **`ndarray`** (^1.0.19)
- **Purpose**: N-dimensional array manipulation
- **Usage**: Core data structure for image data processing
- **Files**:
- `src/input/input_stream/input_stream_base.ts` - NdArray type definitions
- `src/input/input_stream/input_stream.ts` - NdArray for frame data
- `src/input/frame_grabber.ts` - Ndarray for frame manipulation
- `src/vendor.d.ts` - Type definitions
- **`ndarray-linear-interpolate`** (^1.0.0)
- **Purpose**: Bilinear interpolation for ndarray data
- **Usage**: Image scaling and transformations
- **Files**:
- `src/input/frame_grabber.ts` - `d2()` method for 2D interpolation
- **`ndarray-pixels`** (^5.0.1)
- **Purpose**: Convert between image formats and ndarray
- **Usage**: Loading image data from various sources
- **Files**:
- `src/input/input_stream/input_stream.ts` - `getPixels()` for image loading
### Polyfills (Deprecated)
- **`@babel/polyfill`** (^7.12.1)
- **Status**: ⚠️ **DEPRECATED** by Babel team
- **Purpose**: Legacy polyfill for ES6+ features
- **Current Usage**: Not directly imported in source code
- **Recommendation**: Should be removed in favor of `core-js` + `regenerator-runtime` or Babel's automatic polyfill injection
- **Migration Path**: Use `@babel/preset-env` with `useBuiltIns: 'usage'` and explicit `core-js@3`
---
## Build & Development Tools
These packages are **only used during build/development** and are **not bundled into the final output**:
### TypeScript Toolchain
- **`typescript`** (^5.9.3) - TypeScript compiler
- **`@types/*`** packages - Type definitions for TypeScript
- `@types/chai`, `@types/gl-vec2`, `@types/lodash`, `@types/mocha`, `@types/node`, `@types/sinon`, `@types/sinon-chai`
### Webpack & Bundling
- **`webpack`** (^4.44.2) - Module bundler (used to create `dist/` and `lib/` outputs)
- **`webpack-cli`** (^3.3.12) - Webpack command-line interface
- **`babel-loader`** (^8.2.5) - Webpack loader for Babel transpilation
- **`source-map-loader`** (^1.1.1) - Webpack loader for source maps
### Babel Transpilation
- **`@babel/core`** (^7.28.5) - Babel compiler core
- **`@babel/preset-env`** (^7.28.5) - Smart transpilation based on target environments
- **`@babel/preset-typescript`** (^7.28.5) - TypeScript support in Babel
- **`@babel/plugin-*`** - Various syntax plugins:
- `@babel/plugin-proposal-class-properties`
- `@babel/plugin-proposal-nullish-coalescing-operator`
- `@babel/plugin-proposal-object-rest-spread`
- `@babel/plugin-proposal-optional-chaining`
- `@babel/plugin-transform-runtime`
- **`@babel/runtime`** (^7.28.4) - Babel runtime helpers
- **`babel-plugin-add-module-exports`** (^1.0.4) - CommonJS module.exports handling
- **`babel-plugin-istanbul`** (^7.0.1) - Code coverage instrumentation
### Testing
- **`mocha`** (^5.2.0) - Test framework
- **`chai`** (^4.3.10) - Assertion library
- **`sinon`** (^21.0.0) - Test spies, stubs, and mocks
- **`sinon-chai`** (^3.7.0) - Sinon assertions for Chai
- **`ts-mocha`** (^11.1.0) - TypeScript support for Mocha
- **`ts-node`** (^10.9.2) - TypeScript execution for Node.js
- **`cypress`** (^13.1.0) - End-to-end browser testing
- **`@cypress/webpack-preprocessor`** (6.0.0) - Webpack integration for Cypress
- **`@cypress/code-coverage`** (^3.12.4) - Code coverage for Cypress tests
- **`nyc`** (^17.1.0) - Code coverage tool (Istanbul wrapper)
### Linting & Code Quality
- **`eslint`** (^8.57.1) - JavaScript/TypeScript linter
- **`@typescript-eslint/eslint-plugin`** (^7.18.0) - TypeScript-specific ESLint rules
- **`@typescript-eslint/parser`** (^7.18.0) - TypeScript parser for ESLint
- **`eslint-config-airbnb-base`** (^15.0.0) - Airbnb JavaScript style guide
- **`eslint-config-airbnb-typescript`** (^18.0.0) - Airbnb style for TypeScript
- **`eslint-config-airbnb-typescript-base`** (^6.0.1) - Base Airbnb TypeScript config
- **`eslint-plugin-import`** (^2.32.0) - Import/export validation
- **`eslint-plugin-jsx-a11y`** (^6.10.2) - Accessibility linting
- **`eslint-plugin-typescript-sort-keys`** (^3.3.0) - Enforce sorted object keys
### Utilities
- **`core-js`** (^3.46.0) - Modern JavaScript polyfills (used by Babel)
- **`cross-env`** (^10.1.0) - Cross-platform environment variable setting
---
## Optional Dependencies
- **`fsevents`** (2.3.3)
- **Platform**: macOS only
- **Purpose**: Native file watching for better performance
- **Usage**: Automatically used by Webpack/build tools on macOS
---
## Overrides
The `overrides` field forces specific versions of transitive dependencies:
```json
"overrides": {
"@cypress/request": "^3.0.9"
}
```
- **Purpose**: Security fix for `form-data` vulnerability (CVE in versions < 2.5.4)
- **Details**: Cypress 13.1.0 bundles `@cypress/request@3.0.0` which depends on vulnerable `form-data@2.3.3`. This override forces `@cypress/request@^3.0.9` which uses safe `form-data@~4.0.4`.
---
## Bundle Size Impact
When evaluating dependencies, consider their impact on bundle size:
| Package | Approximate Size | Bundled? |
|---------|-----------------|----------|
| `gl-matrix` | ~50 KB (minified) | ✅ Yes |
| `lodash` | ~4 KB (only `merge` + `pick`) | ✅ Yes (tree-shaken) |
| `ndarray` | ~5 KB | ✅ Yes |
| `ndarray-linear-interpolate` | ~2 KB | ✅ Yes |
| `ndarray-pixels` | ~10 KB | ✅ Yes (browser) |
| `webpack` | ~1.5 MB | ❌ No (dev only) |
| `typescript` | ~50 MB | ❌ No (dev only) |
---
## Adding New Dependencies
When adding a new dependency, consider:
1. **Is it a runtime dependency?**
- Will the code be `import`ed in `src/` files?
- Will it be bundled into `dist/` or `lib/` output?
- → Add to `devDependencies` (all deps go here due to bundling)
- → Document it in the "Runtime Code Dependencies" section above
2. **Is it a build/test tool?**
- Is it only used by Webpack, Babel, ESLint, Mocha, etc.?
- → Add to `devDependencies`
- → Document it in the "Build & Development Tools" section above
3. **Bundle size impact?**
- Run `npm run build` and check the size change in `dist/quagga.min.js`
- Consider tree-shaking (does the library support ES modules?)
- Look for lighter alternatives if the package is large
4. **Browser compatibility?**
- Does the package work in browsers?
- Does it require Node.js-specific APIs (`fs`, `path`, etc.)?
- → Check if it's already shimmed in `configs/webpack.config.js` (e.g., `node: { fs: 'empty' }`)
---
## Version Constraints
### Pinned Versions
Some packages are pinned to specific versions due to compatibility issues:
- **`mocha@^5.2.0`** - Pinned to v5 because newer versions have breaking changes
- **`chai@^4.3.10`** - Pinned to v4 because v5+ and v6+ are ESM-only, incompatible with CommonJS tests
- **`sinon-chai@^3.7.0`** - Pinned to match `chai@4.x` compatibility
- **`webpack@^4.44.2`** - Pinned to v4 because v5 requires significant config migration
- **`cypress@^13.1.0`** - Pinned to v13 for stability
These are configured in `.ncurc.json` to prevent accidental upgrades via `npm-check-updates`.
### Upgrade Policy
- **TypeScript ecosystem** (`typescript`, `@typescript-eslint/*`, `ts-*`): Keep up-to-date
- **Babel ecosystem** (`@babel/*`): Keep up-to-date for security and features
- **Testing tools** (`mocha`, `chai`, `sinon`): Upgrade cautiously, test thoroughly
- **Webpack & bundlers**: Major version upgrades require careful migration planning
- **Runtime dependencies** (`gl-matrix`, `lodash`, `ndarray*`): Keep up-to-date unless breaking changes occur
---
## Security Considerations
### Known Issues
1. **`@babel/polyfill` is deprecated** - Should migrate to `core-js@3` + `regenerator-runtime`
2. **Old `mocha` version** - v5.2.0 is from 2018, may have unpatched vulnerabilities
3. **Webpack 4** - No longer receives updates, consider upgrading to Webpack 5
### Monitoring
- Run `npm audit` regularly to check for vulnerabilities
- Use `npm run check-updates` to see available updates
- Check GitHub Dependabot alerts
---
## FAQ
**Q: Why are runtime dependencies in `devDependencies` instead of `dependencies`?**
A: Quagga2 is a **bundled library**. Consumers install the package and use the pre-built files (`dist/quagga.min.js` or `lib/quagga.js`), not the source code. They never run `npm install` on Quagga2's dependencies. Therefore, from npm's perspective, all packages are development dependencies (used during build), not runtime dependencies (used after install).
**Q: How can I tell if a package is actually used in the code?**
A: Search the `src/` directory:
```bash
# Search for imports
grep -r "from 'package-name'" src/
grep -r 'from "package-name"' src/
grep -r "require('package-name')" src/
```
**Q: What's the difference between `optionalDependencies` and `devDependencies`?**
A: `optionalDependencies` are packages that enhance functionality if available but aren't required (like `fsevents` for macOS file watching). `devDependencies` are required for development but not for using the published package.
**Q: Can I remove `@babel/polyfill`?**
A: Yes, but carefully. It's deprecated and not directly imported anymore. Remove it from `package.json` and verify that `@babel/preset-env` is configured to polyfill features automatically via `core-js@3`. Test thoroughly in older browsers (IE11, older Safari) after removal.
**Q: Why can't I upgrade `chai` to version 5 or 6?**
A: `chai@5+` and `chai@6+` are ESM-only (ES modules). Quagga2's tests use CommonJS (`require()`), and `mocha@5` doesn't support ESM. Upgrading `chai` requires also upgrading `mocha` to v9.1.0+ and migrating all test files to ESM syntax.
---
## Related Files
- **`package.json`** - Dependency declarations
- **`.ncurc.json`** - npm-check-updates configuration (blocks unsafe auto-upgrades)
- **`configs/webpack.config.js`** - Build configuration showing which dependencies are bundled
- **`configs/webpack.node.config.js`** - Node.js-specific build configuration
- **`CHANGELOG.md`** - Version history and dependency changes
---
## Maintenance Notes
This document was created in November 2025 following the TypeScript 5.9.3 upgrade. It should be updated whenever:
- A new dependency is added or removed
- A major version upgrade changes behavior
- Security vulnerabilities are discovered and patched
- Build tooling changes significantly
Last updated: 2025-11-16

View File

@@ -0,0 +1,74 @@
# Reference Documentation
Precise technical descriptions of Quagga2's API, configuration, and capabilities. Consult these when you need exact details about how something works.
## Core API
### [API Documentation](api.md)
Complete reference for all Quagga2 methods, callbacks, and events.
### [Configuration Options](configuration.md)
Detailed documentation of every configuration parameter and its effects.
### [Camera Access API](camera-access.md)
Methods for controlling camera access, torch/flash, and device enumeration.
## Barcode Support
### [Supported Barcode Types](readers.md)
List of all supported barcode formats with characteristics and use cases.
## Compatibility
### [Browser Support](browser-support.md)
Browser compatibility matrix and required Web APIs.
## Development
### [Dependencies](dependencies.md)
Explanation of all package dependencies - which are bundled vs. dev-only.
## Quick Lookup
| Need | See |
|------|-----|
| Method signatures | [API Documentation](api.md) |
| Config parameter details | [Configuration Options](configuration.md) |
| Which browsers work | [Browser Support](browser-support.md) |
| Which barcodes supported | [Supported Barcode Types](readers.md) |
| Camera control | [Camera Access API](camera-access.md) |
| Package versions | [Dependencies](dependencies.md) |
## Reading Reference Docs
Reference documentation is:
- **Information-oriented** - Focuses on describing *what exists*
- **Accurate** - Every detail matters, kept up-to-date with code
- **Complete** - Covers all features, even obscure ones
- **Structured** - Organized for lookup, not linear reading
Don't read reference docs cover-to-cover. Instead:
1. **Look up** what you need when you need it
2. **Verify** assumptions about how things work
3. **Discover** features you didn't know existed
4. **Confirm** exact method signatures or parameter types
## Need Context?
Reference docs tell you *what* and *how*, but not *why*:
- For **why** something works a certain way → See [Explanation](../explanation/)
- For **how to use** something in practice → See [How-To Guides](../how-to-guides/)
- For **learning** from scratch → See [Tutorials](../tutorials/)
---
[← Back to Documentation Home](../index.md)

View File

@@ -0,0 +1,326 @@
# Supported Barcode Types {#supported-barcode-types}
Quagga2 supports a wide variety of 1D barcode formats. This page lists all available barcode readers and how to configure them.
## Available Readers {#available-readers}
Quagga2 includes built-in readers for the following barcode formats:
| Reader Name | Barcode Format | Common Uses |
|-------------|----------------|-------------|
| `code_128_reader` | [Code 128](https://en.wikipedia.org/wiki/Code_128), [GS1-128](https://en.wikipedia.org/wiki/GS1-128) | General purpose, shipping, packaging, supply chain |
| `ean_reader` | [EAN-13](https://en.wikipedia.org/wiki/International_Article_Number) | Retail products worldwide |
| `ean_8_reader` | [EAN-8](https://en.wikipedia.org/wiki/EAN-8) | Small retail products |
| `code_39_reader` | [Code 39](https://en.wikipedia.org/wiki/Code_39) | Automotive, defense, healthcare |
| `code_39_vin_reader` | Code 39 VIN | Vehicle Identification Numbers |
| `codabar_reader` | [Codabar](https://en.wikipedia.org/wiki/Codabar) | Libraries, blood banks, logistics |
| `upc_reader` | [UPC-A](https://en.wikipedia.org/wiki/Universal_Product_Code) | Retail products (North America) |
| `upc_e_reader` | [UPC-E](https://en.wikipedia.org/wiki/Universal_Product_Code#UPC-E) | Small retail products |
| `i2of5_reader` | [Interleaved 2 of 5](https://en.wikipedia.org/wiki/Interleaved_2_of_5) | Warehouse, distribution |
| `2of5_reader` | [Standard 2 of 5](https://en.wikipedia.org/wiki/Two-out-of-five_code) | Industrial, airline tickets |
| `code_93_reader` | [Code 93](https://en.wikipedia.org/wiki/Code_93) | Logistics, retail |
| `code_32_reader` | [Code 32](https://en.wikipedia.org/wiki/Pharmacode#Code_32) | Italian pharmaceuticals |
| `pharmacode_reader` | [Pharmacode](https://en.wikipedia.org/wiki/Pharmacode) | Pharmaceutical packaging |
## Basic Configuration {#basic-configuration}
Specify which barcode types to detect in the `decoder.readers` array:
```javascript
Quagga.init({
decoder: {
readers: ["code_128_reader"] // Only detect Code 128
}
}, function(err) {
if (err) {
console.error(err);
return;
}
Quagga.start();
});
```
### Multiple Readers {#multiple-readers}
You can enable multiple readers to detect different barcode types:
```javascript
Quagga.init({
decoder: {
readers: [
"code_128_reader",
"ean_reader",
"upc_reader"
]
}
});
```
## Important Considerations {#important-considerations}
### Reader Priority and Order {#reader-priority-and-order}
**Readers are processed in the exact order they appear in the `readers` array.** The first reader to successfully decode the barcode wins - subsequent readers are not tried.
This allows you to prioritize certain formats over others when multiple formats might match the same barcode pattern:
```javascript
decoder: {
// EAN-13 checked first, then UPC formats
readers: ['ean_reader', 'upc_reader', 'upc_e_reader']
}
```
**Why order matters:**
- Readers are processed sequentially, not in parallel
- Some readers may return false positives for other formats
- Example: EAN-13 and UPC-A/UPC-E share similar patterns and can clash
- The first successful decode is returned immediately
**Best practice**: List your most commonly expected barcode types first for best accuracy and performance.
### Don't Enable All Readers {#dont-enable-all-readers}
**Why not enable all readers by default?**
- More readers = more processing time
- Increased chance of false positives
- Some formats overlap and can interfere
**Best practice**: Only enable the barcode formats you actually need to scan.
### Format Conflicts {#format-conflicts}
Some barcode formats are subsets or extensions of others:
- **UPC-A** is a subset of **EAN-13**
- **EAN-8** is shorter version of **EAN-13**
- **Code 39** and **Code 39 VIN** share similar patterns
Be careful when enabling multiple related formats together.
## GS1-128 Barcodes {#gs1-128-barcodes}
[GS1-128](https://en.wikipedia.org/wiki/GS1-128) (formerly known as EAN-128 or UCC-128) is a subset of Code 128 used extensively in supply chain and logistics. It uses special FNC1 (Function Code 1) characters to separate variable-length data fields called Application Identifiers (AIs).
### How GS1-128 Works {#gs1-128-how-it-works}
GS1-128 barcodes encode structured data using standardized Application Identifiers. For example:
- **AI 01** = GTIN (Global Trade Item Number)
- **AI 10** = Batch/Lot Number
- **AI 17** = Expiration Date
- **AI 21** = Serial Number
The FNC1 character acts as a field separator between variable-length AIs, allowing decoders to know where one field ends and another begins.
### FNC1 Character Handling {#fnc1-character-handling}
When the `code_128_reader` decodes a GS1-128 barcode, FNC1 characters are represented as ASCII 29 (Group Separator, `\x1D` or `\u001d`). This follows the GS1 standard for representing FNC1 in decoded data.
```javascript
Quagga.decodeSingle({
src: 'gs1-128-barcode.jpg',
decoder: {
readers: ['code_128_reader']
}
}, function(result) {
if (result && result.codeResult) {
const code = result.codeResult.code;
// FNC1 characters appear as ASCII 29 (Group Separator)
const GS = String.fromCharCode(29); // '\x1D'
// Split on Group Separator to get individual AI fields
const fields = code.split(GS);
console.log('Fields:', fields);
// Example output: ["", "01034531200000111719050810ABCD1234", ...]
// Or check for GS1-128 format (starts with FNC1)
if (code.startsWith(GS)) {
console.log('This is a GS1-128 barcode');
}
}
});
```
### Parsing GS1-128 Data {#parsing-gs1-128-data}
Once decoded, you can parse the GS1-128 data using the Application Identifier structure:
```javascript
function parseGS1(code) {
const GS = String.fromCharCode(29);
// Remove leading FNC1 if present
const data = code.startsWith(GS) ? code.substring(1) : code;
// Split by FNC1 separator
const segments = data.split(GS);
// Parse each segment for its AI
// (A full implementation would use a complete AI table)
return segments;
}
```
For full GS1 parsing, consider using a dedicated library like [gs1-barcode-parser](https://www.npmjs.com/package/gs1-barcode-parser) after decoding with Quagga2.
## EAN Extensions {#ean-extensions}
### EAN-2 and EAN-5 Supplements {#ean-supplements}
The EAN and UPC barcode formats support a supplement format, adding an additional 2 or 5 digits beyond the main barcode, EAN-2 and EAN-5, respectively. They are typically used for:
- **Magazines and periodicals**: The main barcode identifies the publication, while the supplement denotes issue numbers or publication dates
- **Books with ISBN**: The 5-digit supplement often encodes the suggested retail price
By default, `ean_reader` does not read and decode these extensions, you must explicitly enable support for them, if you are looking for them. Since UPC-A is a subset of EAN-13 -- UPC-A codes are EAN-13 codes that begin with a 0 -- supplement support configured on `ean_reader` also works for UPC-A codes.
To enable supplement decoding:
```javascript
Quagga.init({
decoder: {
readers: [{
format: "ean_reader",
config: {
supplements: [
'ean_5_reader', // 5-digit supplement
'ean_2_reader' // 2-digit supplement
]
}
}]
}
});
```
#### Supplement Result Structure {#supplement-result-structure}
When a barcode with a supplement is decoded, the result includes a `supplement` property:
```javascript
{
codeResult: {
code: "419871600890101", // Combined: main barcode + supplement
format: "ean_13", // Main barcode format
supplement: {
code: "01", // Supplement digits only
format: "ean_2" // "ean_2" or "ean_5"
}
}
}
```
The main `codeResult.code` contains the full combined value, while `codeResult.supplement` provides the supplement details separately.
### Important Notes About Supplements {#supplements-notes}
**Supplement order matters**: The reader stops when it finds the first matching supplement. List `ean_5_reader` before `ean_2_reader` if you want to prioritize 5-digit extensions.
**Cannot read regular EAN-13 with supplements enabled**: If you configure supplements, that reader instance can **only** decode EAN codes **with** supplements. To read both:
```javascript
Quagga.init({
decoder: {
readers: [
"ean_reader", // Regular EAN-13 without supplements
{
format: "ean_reader",
config: {
supplements: ['ean_5_reader', 'ean_2_reader']
}
} // EAN-13 with supplements
]
}
});
```
This configuration creates two separate reader instances.
## External Readers {#external-readers}
Quagga2 supports external reader modules for additional barcode formats not built into the core library.
### QR Code Reader {#qr-code-reader}
For QR code support, see [quagga2-reader-qr](https://github.com/ericblade/quagga2-reader-qr).
External readers extend Quagga2's capabilities beyond 1D barcodes:
```javascript
import Quagga from '@ericblade/quagga2';
import QRReader from 'quagga2-reader-qr';
// Register external reader
Quagga.registerReader('qr', QRReader);
Quagga.init({
decoder: {
readers: ['qr'] // Use external QR reader
}
});
```
### External Reader Priority {#external-reader-priority}
External readers follow the **same priority rules** as built-in readers. Once registered with `Quagga.registerReader()`, an external reader can be placed anywhere in the `readers` array, and its position determines when it attempts to decode relative to other readers:
```javascript
// Register external reader first
Quagga.registerReader('my_custom_reader', MyCustomReader);
// Use in config - position determines priority
Quagga.init({
decoder: {
// External reader tried first, then built-in readers
readers: ['my_custom_reader', 'ean_reader', 'code_128_reader']
}
});
```
**Key points:**
- External readers must be registered via `registerReader()` before use
- Their position in `readers` array determines decode priority
- There is no automatic "internal first, external second" ordering
- External readers interleave freely with built-in readers
### Creating Custom Readers {#creating-custom-readers}
You can create your own barcode reader implementations by extending the `BarcodeReader` prototype exported by Quagga2. See [How-To: Create External Readers](../how-to-guides/external-readers.md) for details.
## Reader Performance {#reader-performance}
Different readers have different performance characteristics:
**Fastest readers**:
- `code_128_reader` - Optimized, widely used
- `ean_reader` - Fast and reliable
**Slower readers**:
- `code_39_reader` - More complex pattern
- `i2of5_reader` - Requires more validation
**Resource intensive**:
- Multiple readers enabled simultaneously
- Readers with supplements configured
## Validation {#validation}
Some barcode formats include check digits for validation:
- **EAN-13/EAN-8**: Built-in check digit
- **Code 128**: Built-in check digit
- **UPC-A/UPC-E**: Built-in check digit
For additional validation in your application, consider using [barcode-validator](https://github.com/ericblade/barcode-validator).
## Related {#related}
- [Configuration Reference](configuration.md) - Complete config options
- [API Documentation](api.md) - How to use Quagga2 methods
- [Tips & Tricks](../how-to-guides/tips-and-tricks.md) - Handling false positives
---
[← Back to Reference](index.md)

View File

@@ -0,0 +1,141 @@
# Your First Barcode Scan {#first-scan}
This tutorial walks you through creating a simple barcode scanner using your device's camera.
## Prerequisites {#prerequisites}
- A web browser that supports camera access (Chrome, Firefox, Safari, Edge)
- A device with a camera
- Basic knowledge of HTML and JavaScript
## Step 1: Set Up the HTML {#step-1-html}
Create an `index.html` file:
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Barcode Scanner</title>
<style>
#scanner-container {
position: relative;
width: 100%;
max-width: 640px;
}
#scanner-container video {
width: 100%;
}
#result {
margin-top: 20px;
padding: 10px;
background: #f0f0f0;
}
</style>
</head>
<body>
<h1>Barcode Scanner</h1>
<div id="scanner-container"></div>
<div id="result">Scan a barcode...</div>
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.min.js"></script>
<script src="app.js"></script>
</body>
</html>
```
## Step 2: Initialize Quagga {#step-2-initialize}
Create an `app.js` file:
```javascript
Quagga.init({
inputStream: {
type: "LiveStream",
target: document.querySelector('#scanner-container'),
constraints: {
facingMode: "environment" // Use back camera
}
},
decoder: {
readers: ["code_128_reader", "ean_reader", "upc_reader"]
}
}, function(err) {
if (err) {
console.error("Failed to initialize:", err);
document.querySelector('#result').textContent = "Error: " + err.message;
return;
}
console.log("Scanner ready");
Quagga.start();
});
```
## Step 3: Handle Detections {#step-3-handle-detections}
Add detection handling to `app.js`:
```javascript
Quagga.onDetected(function(result) {
const code = result.codeResult.code;
const format = result.codeResult.format;
document.querySelector('#result').textContent =
`Found ${format}: ${code}`;
console.log("Barcode detected:", code);
});
```
## Step 4: Run It {#step-4-run}
1. Serve the files using a local web server (required for camera access)
2. Open the page in your browser
3. Allow camera access when prompted
4. Point the camera at a barcode
**Note**: Camera access requires HTTPS on non-localhost domains.
## Complete Code {#complete-code}
Here's the complete `app.js`:
```javascript
Quagga.init({
inputStream: {
type: "LiveStream",
target: document.querySelector('#scanner-container'),
constraints: {
facingMode: "environment"
}
},
decoder: {
readers: ["code_128_reader", "ean_reader", "upc_reader"]
}
}, function(err) {
if (err) {
console.error("Failed to initialize:", err);
document.querySelector('#result').textContent = "Error: " + err.message;
return;
}
Quagga.start();
});
Quagga.onDetected(function(result) {
const code = result.codeResult.code;
const format = result.codeResult.format;
document.querySelector('#result').textContent = `Found ${format}: ${code}`;
});
```
## Next Steps {#next-steps}
- [Static Image Scanning](static-image.md) - Decode images without camera
- [Configuration Reference](../reference/configuration.md) - Customize behavior
- [Tips and Tricks](../how-to-guides/tips-and-tricks.md) - Improve results
---
[← Back to Tutorials](index.md)

View File

@@ -0,0 +1,77 @@
# Tutorials
Step-by-step tutorials to help you learn Quagga2 by building real examples. These are learning-oriented guides designed to help beginners get hands-on experience.
## Available Tutorials
### [Your First Barcode Scan](first-scan.md) ⭐ Start Here
Build a complete barcode scanner web app from scratch. You'll learn how to:
- Set up HTML structure
- Initialize Quagga2
- Handle camera permissions
- Process detected barcodes
- Display results to users
**Time**: 15 minutes | **Difficulty**: Beginner
### [Decoding Static Images](static-image.md)
Learn to decode barcodes from image files instead of camera feeds. Perfect for:
- Photo upload features
- Batch processing
- Server-side Node.js applications
- Testing without a camera
**Time**: 10 minutes | **Difficulty**: Beginner
### [Using with React](react-integration.md)
Integrate Quagga2 into a React application. Covers:
- Component lifecycle management
- Handling cleanup
- State management
- Common pitfalls and solutions
**Time**: 20 minutes | **Difficulty**: Intermediate
### [Using with Node.js](node-usage.md)
Use Quagga2 in server-side Node.js applications. Learn to:
- Process images from filesystem
- Handle image buffers
- Batch decode multiple images
- Build CLI tools
**Time**: 15 minutes | **Difficulty**: Intermediate
## Prerequisites
Before starting these tutorials, you should have:
- Basic HTML/JavaScript knowledge
- A text editor
- A web server (or ability to run `npx serve`)
- A device with a camera (for live scanning tutorials)
## Tutorial Approach
These tutorials follow a **learning-by-doing** approach:
1. **Complete examples** - Each tutorial provides full working code
2. **Progressive complexity** - Start simple, add features incrementally
3. **Hands-on practice** - Type the code yourself to build muscle memory
4. **Real-world scenarios** - Build things you'll actually use
## Need Help?
If you get stuck:
- Check the [API Reference](../reference/api.md) for method details
- Review the [Configuration Guide](../reference/configuration.md)
- Ask in [Gitter Chat](https://gitter.im/quaggaJS/Lobby)
- Search [GitHub Issues](https://github.com/ericblade/quagga2/issues)
## Contributing
Found a bug in a tutorial or have an idea for a new one? [Open an issue](https://github.com/ericblade/quagga2/issues) or submit a pull request!
---
**Next**: Start with [Your First Barcode Scan](first-scan.md) →

View File

@@ -0,0 +1,182 @@
# Using Quagga2 in Node.js {#node-usage}
This tutorial covers server-side barcode scanning with Quagga2 in Node.js.
## Installation {#installation}
```bash
npm install @ericblade/quagga2
```
## Basic Usage {#basic-usage}
```javascript
const Quagga = require('@ericblade/quagga2').default;
Quagga.decodeSingle({
src: './barcode.jpg',
decoder: {
readers: ['code_128_reader', 'ean_reader']
}
}, function(result) {
if (result && result.codeResult) {
console.log('Barcode:', result.codeResult.code);
console.log('Format:', result.codeResult.format);
} else {
console.log('No barcode found');
}
});
```
## With Promises {#with-promises}
Wrap in a Promise for async/await:
```javascript
const Quagga = require('@ericblade/quagga2').default;
function decodeBarcode(imagePath, readers = ['code_128_reader']) {
return new Promise((resolve, reject) => {
Quagga.decodeSingle({
src: imagePath,
decoder: { readers }
}, (result) => {
if (result && result.codeResult) {
resolve(result.codeResult);
} else {
resolve(null);
}
});
});
}
// Usage
async function main() {
const result = await decodeBarcode('./barcode.jpg');
if (result) {
console.log(`Found ${result.format}: ${result.code}`);
}
}
main();
```
## Express API Example {#express-example}
```javascript
const express = require('express');
const multer = require('multer');
const Quagga = require('@ericblade/quagga2').default;
const app = express();
const upload = multer({ dest: 'uploads/' });
app.post('/scan', upload.single('image'), (req, res) => {
if (!req.file) {
return res.status(400).json({ error: 'No image provided' });
}
Quagga.decodeSingle({
src: req.file.path,
decoder: {
readers: ['code_128_reader', 'ean_reader', 'upc_reader']
}
}, (result) => {
if (result && result.codeResult) {
res.json({
code: result.codeResult.code,
format: result.codeResult.format
});
} else {
res.json({ code: null, error: 'No barcode found' });
}
});
});
app.listen(3000, () => {
console.log('Barcode API running on port 3000');
});
```
## Batch Processing {#batch-processing}
Process multiple images:
```javascript
const Quagga = require('@ericblade/quagga2').default;
const fs = require('fs');
const path = require('path');
async function decodeImage(imagePath) {
return new Promise((resolve) => {
Quagga.decodeSingle({
src: imagePath,
decoder: { readers: ['code_128_reader', 'ean_reader'] }
}, (result) => {
resolve({
file: path.basename(imagePath),
code: result?.codeResult?.code || null,
format: result?.codeResult?.format || null
});
});
});
}
async function processDirectory(dir) {
const files = fs.readdirSync(dir)
.filter(f => /\.(jpg|jpeg|png)$/i.test(f));
const results = [];
for (const file of files) {
const result = await decodeImage(path.join(dir, file));
results.push(result);
console.log(`${result.file}: ${result.code || 'No barcode'}`);
}
return results;
}
processDirectory('./images');
```
## Configuration Tips {#configuration-tips}
### Image Size {#image-size}
Control processing resolution:
```javascript
Quagga.decodeSingle({
src: './large-image.jpg',
inputStream: {
size: 1280 // Scale to max 1280px
},
decoder: { readers: ['ean_reader'] }
}, callback);
```
### Locator Settings {#locator-settings}
For difficult images:
```javascript
Quagga.decodeSingle({
src: './image.jpg',
locate: true,
locator: {
patchSize: 'small',
halfSample: false
},
decoder: { readers: ['code_128_reader'] }
}, callback);
```
## Related {#related}
- [Static Image Scanning](static-image.md) - Browser-side image decoding
- [API Reference](../reference/api.md#quagga-decodesingle) - `decodeSingle()` details
- [Configuration Reference](../reference/configuration.md) - All options
---
[← Back to Tutorials](index.md)

View File

@@ -0,0 +1,176 @@
# Using Quagga2 with React {#react-integration}
This tutorial shows how to integrate Quagga2 into a React application.
## Installation {#installation}
```bash
npm install @ericblade/quagga2
```
## Basic Component {#basic-component}
```jsx
import React, { useEffect, useRef, useCallback } from 'react';
import Quagga from '@ericblade/quagga2';
function BarcodeScanner({ onDetected }) {
const scannerRef = useRef(null);
const handleDetected = useCallback((result) => {
if (result.codeResult) {
onDetected(result.codeResult.code);
}
}, [onDetected]);
useEffect(() => {
Quagga.init({
inputStream: {
type: 'LiveStream',
target: scannerRef.current,
constraints: {
facingMode: 'environment'
}
},
decoder: {
readers: ['code_128_reader', 'ean_reader']
}
}, (err) => {
if (err) {
console.error('Failed to initialize:', err);
return;
}
Quagga.start();
});
Quagga.onDetected(handleDetected);
return () => {
Quagga.offDetected(handleDetected);
Quagga.stop();
};
}, [handleDetected]);
return <div ref={scannerRef} style={{ width: '100%' }} />;
}
export default BarcodeScanner;
```
## Usage {#usage}
```jsx
import BarcodeScanner from './BarcodeScanner';
function App() {
const handleScan = (code) => {
console.log('Scanned:', code);
alert(`Barcode: ${code}`);
};
return (
<div>
<h1>Scan a Barcode</h1>
<BarcodeScanner onDetected={handleScan} />
</div>
);
}
```
## With Hooks {#with-hooks}
Create a reusable hook:
```jsx
import { useEffect, useRef, useState } from 'react';
import Quagga from '@ericblade/quagga2';
export function useQuagga(config = {}) {
const scannerRef = useRef(null);
const [scanning, setScanning] = useState(false);
const [result, setResult] = useState(null);
useEffect(() => {
if (!scannerRef.current) return;
Quagga.init({
inputStream: {
type: 'LiveStream',
target: scannerRef.current,
...config.inputStream
},
decoder: {
readers: ['code_128_reader'],
...config.decoder
},
...config
}, (err) => {
if (!err) {
Quagga.start();
setScanning(true);
}
});
Quagga.onDetected((data) => {
setResult(data.codeResult);
});
return () => {
Quagga.stop();
setScanning(false);
};
}, []);
return { scannerRef, scanning, result };
}
```
## Stop on Detection {#stop-on-detection}
```jsx
function SingleScanComponent({ onComplete }) {
const scannerRef = useRef(null);
useEffect(() => {
Quagga.init({
inputStream: {
type: 'LiveStream',
target: scannerRef.current
},
decoder: {
readers: ['ean_reader']
}
}, (err) => {
if (!err) Quagga.start();
});
const handleDetected = (result) => {
Quagga.stop();
onComplete(result.codeResult.code);
};
Quagga.onDetected(handleDetected);
return () => {
Quagga.offDetected(handleDetected);
Quagga.stop();
};
}, [onComplete]);
return <div ref={scannerRef} />;
}
```
## Resources {#resources}
- [quagga2-react-example](https://github.com/ericblade/quagga2-react-example/) - Complete example
- [quagga2-redux-middleware](https://github.com/ericblade/quagga2-redux-middleware/) - Redux integration
## Related {#related}
- [API Reference](../reference/api.md) - Full API documentation
- [Configuration Reference](../reference/configuration.md) - All options
---
[← Back to Tutorials](index.md)

View File

@@ -0,0 +1,130 @@
# Decoding Static Images {#static-image}
This tutorial shows how to decode barcodes from image files instead of a live camera feed.
## Basic Usage {#basic-usage}
Use `Quagga.decodeSingle()` to decode a single image:
```javascript
Quagga.decodeSingle({
src: '/path/to/barcode.jpg',
decoder: {
readers: ["code_128_reader", "ean_reader"]
}
}, function(result) {
if (result && result.codeResult) {
console.log("Barcode:", result.codeResult.code);
} else {
console.log("No barcode found");
}
});
```
## From File Input {#from-file-input}
Allow users to upload images:
### HTML
```html
<input type="file" id="file-input" accept="image/*">
<div id="result"></div>
```
### JavaScript
```javascript
document.querySelector('#file-input').addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(event) {
Quagga.decodeSingle({
src: event.target.result,
decoder: {
readers: ["code_128_reader", "ean_reader", "upc_reader"]
}
}, function(result) {
if (result && result.codeResult) {
document.querySelector('#result').textContent =
"Found: " + result.codeResult.code;
} else {
document.querySelector('#result').textContent = "No barcode found";
}
});
};
reader.readAsDataURL(file);
});
```
## Controlling Image Size {#controlling-image-size}
By default, `decodeSingle()` scales images to 800px. Adjust with `inputStream.size`:
```javascript
Quagga.decodeSingle({
src: '/path/to/image.jpg',
inputStream: {
size: 1280 // Process at higher resolution
},
decoder: {
readers: ["ean_reader"]
}
}, callback);
```
Set `size: 0` to use original image dimensions.
## With Localization {#with-localization}
Enable `locate: true` (default) to find barcodes anywhere in the image:
```javascript
Quagga.decodeSingle({
src: '/path/to/image.jpg',
locate: true,
locator: {
patchSize: "medium",
halfSample: true
},
decoder: {
readers: ["code_128_reader"]
}
}, function(result) {
if (result && result.codeResult) {
console.log("Found:", result.codeResult.code);
console.log("Location:", result.box); // Bounding box
}
});
```
## Node.js Usage {#nodejs-usage}
Quagga2 works in Node.js:
```javascript
const Quagga = require('@ericblade/quagga2').default;
Quagga.decodeSingle({
src: './barcode.jpg',
decoder: {
readers: ["code_128_reader"]
}
}, function(result) {
if (result && result.codeResult) {
console.log("Barcode:", result.codeResult.code);
}
});
```
## Related {#related}
- [Your First Scan](first-scan.md) - Live camera scanning
- [Node.js Usage](node-usage.md) - Server-side scanning
- [API Reference](../reference/api.md#quagga-decodesingle) - `decodeSingle()` details
---
[← Back to Tutorials](index.md)