Added scanning barcodes with a camera
This commit is contained in:
@@ -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)
|
||||
117
quagga2/quagga2-1.12.1/docs/explanation/architecture.md
Normal file
117
quagga2/quagga2-1.12.1/docs/explanation/architecture.md
Normal 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)
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
Each color represents a distinct labeled component (line). Notice how each cell is processed independently.
|
||||
|
||||
Here are zoomed examples of two cells:
|
||||
|
||||

|
||||
*Good: Parallel lines indicate a possible barcode*
|
||||
|
||||

|
||||
*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:
|
||||
|
||||

|
||||
|
||||
Where μ (mu) are central moments calculated from raw moments (M):
|
||||
|
||||

|
||||
|
||||
The centroid (x̄, ȳ) is calculated from raw moments:
|
||||
|
||||

|
||||
|
||||
And the raw moments (M) are computed as:
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||

|
||||
*Patches rotated to horizontal, bounding box calculated*
|
||||
|
||||

|
||||
*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!
|
||||
80
quagga2/quagga2-1.12.1/docs/explanation/index.md
Normal file
80
quagga2/quagga2-1.12.1/docs/explanation/index.md
Normal 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)
|
||||
270
quagga2/quagga2-1.12.1/docs/explanation/input-streams.md
Normal file
270
quagga2/quagga2-1.12.1/docs/explanation/input-streams.md
Normal 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)
|
||||
Reference in New Issue
Block a user