Compare commits
2 Commits
6f0662271f
...
testing
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a37e5dd5f | |||
| b4f8489834 |
131
README.md
@@ -9,7 +9,7 @@ Webpage Playground is a lightweight, responsive web application for managing and
|
|||||||
- **Dynamic Navigation** — Easy page switching with a responsive navbar
|
- **Dynamic Navigation** — Easy page switching with a responsive navbar
|
||||||
- **Inventory Demo** — Display items using reusable item components
|
- **Inventory Demo** — Display items using reusable item components
|
||||||
- **Advanced Search** — Filter inventory by name, location, quantity range, and expiry date
|
- **Advanced Search** — Filter inventory by name, location, quantity range, and expiry date
|
||||||
- **Barcode Scanner** — Scan barcodes for inventory management (keyboard and hardware scanner support)
|
- **Barcode Scanner** — Scan barcodes using camera or keyboard (hardware scanner support)
|
||||||
- **Data Visualization** — Pie chart showing inventory distribution
|
- **Data Visualization** — Pie chart showing inventory distribution
|
||||||
- **Responsive Design** — Works on desktop, tablet, and mobile devices
|
- **Responsive Design** — Works on desktop, tablet, and mobile devices
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ Webpage Playground is a lightweight, responsive web application for managing and
|
|||||||
### Pages
|
### Pages
|
||||||
- `index.html` — Homepage with item component demo and inventory pie chart
|
- `index.html` — Homepage with item component demo and inventory pie chart
|
||||||
- `search.html` — Search and filter inventory by name, location, quantity, and expiry date
|
- `search.html` — Search and filter inventory by name, location, quantity, and expiry date
|
||||||
- `barcode.html` — Barcode scanner with console logging for testing
|
- `barcode.html` — Barcode scanner with camera and keyboard input modes
|
||||||
- `pantry.html` — Pantry inventory container
|
- `pantry.html` — Pantry inventory container
|
||||||
- `fridge.html` — Fridge inventory container
|
- `fridge.html` — Fridge inventory container
|
||||||
- `freezer.html` — Freezer inventory container
|
- `freezer.html` — Freezer inventory container
|
||||||
@@ -55,7 +55,7 @@ Advanced inventory search and filtering interface.
|
|||||||
- Search by item name (case-insensitive)
|
- Search by item name (case-insensitive)
|
||||||
- Filter by storage location (All, Pantry, Fridge, Freezer)
|
- Filter by storage location (All, Pantry, Fridge, Freezer)
|
||||||
- Filter by quantity range (min/max values)
|
- Filter by quantity range (min/max values)
|
||||||
- **NEW: Filter by expiry date range (start date and end date)**
|
- Filter by expiry date range (start date and end date)
|
||||||
- Real-time result display with item cards
|
- Real-time result display with item cards
|
||||||
- Visual expiry status indicators (Fresh, Expiring Soon, Expired)
|
- Visual expiry status indicators (Fresh, Expiring Soon, Expired)
|
||||||
- Reset button to clear all filters
|
- Reset button to clear all filters
|
||||||
@@ -78,38 +78,61 @@ Results show all items with expiry dates in that range
|
|||||||
Items display formatted dates and expiry status badges
|
Items display formatted dates and expiry status badges
|
||||||
```
|
```
|
||||||
|
|
||||||
**Available Items (20 total across all locations):**
|
|
||||||
- Pantry: Pasta (120 days), Rice (180 days), Cereal (60 days), Flour (150 days), Sugar (200 days), Salt (365 days), Olive Oil (90 days), Canned Beans (EXPIRED -10 days)
|
|
||||||
- Fridge: Milk (5 days), Cheese (30 days), Greek Yogurt (7 days), Eggs (21 days), Butter (45 days), Chicken Salad (EXPIRED -2 days)
|
|
||||||
- Freezer: Ice Cream (90 days), Frozen Vegetables (180 days), Chicken Breast (120 days), Ground Beef (150 days), Pizza (200 days), Ice (365 days)
|
|
||||||
|
|
||||||
### 📱 Barcode Scanner (`barcode.html`)
|
### 📱 Barcode Scanner (`barcode.html`)
|
||||||
Barcode capture interface with console logging for testing.
|
Dual-mode barcode scanning interface with keyboard input and camera scanning.
|
||||||
|
|
||||||
**Features:**
|
**Features:**
|
||||||
- Keyboard input simulation (type barcode + press Enter)
|
|
||||||
- Hardware barcode scanner device support (ready for integration)
|
#### Keyboard Input Mode
|
||||||
- Paste support (Ctrl/Cmd+V)
|
- Type or paste barcode values
|
||||||
- Console logging with formatted output including:
|
- Hardware barcode scanner device support
|
||||||
- Timestamp (HH:MM:SS.mmm format)
|
- Real-time console logging
|
||||||
- Barcode value
|
- Auto-focus field for seamless entry
|
||||||
- Input type detection (keyboard, hardware-scanner, keyboard-paste)
|
- Enter key to scan
|
||||||
- Metadata for debugging
|
|
||||||
- Auto-focus input field after each scan
|
#### Camera Scanning Mode (**NEW**)
|
||||||
- Visual status indicator
|
- **Real-time barcode detection** using device camera
|
||||||
- Helper instructions and "Open Console" button
|
- **Supported formats:** UPC-A, UPC-E, EAN-13, EAN-8, Code-128, Code-39, and more
|
||||||
|
- **Browser-based detection** (uses Quagga2 library - local version)
|
||||||
|
- **Mode toggle** between keyboard and camera input
|
||||||
|
- **Camera controls** with start/stop buttons
|
||||||
|
- **Real-time feedback** showing detected barcodes
|
||||||
|
- **Error handling** with user-friendly messages
|
||||||
|
- **Mobile support** — Works on iOS and Android devices with camera access
|
||||||
|
- **Console logging** of all detections with metadata
|
||||||
|
|
||||||
|
**Camera Usage:**
|
||||||
|
1. Switch to "Camera Scan" mode by clicking the button
|
||||||
|
2. Click "Start Camera" to begin
|
||||||
|
3. Browser will request camera permission (grant access)
|
||||||
|
4. Position barcode in front of camera
|
||||||
|
5. Barcode is detected automatically in real-time
|
||||||
|
6. Detected barcodes logged to console (press F12)
|
||||||
|
7. Click "Stop Camera" to end scanning
|
||||||
|
|
||||||
|
**Keyboard Usage:**
|
||||||
|
1. Stay in "Keyboard Input" mode
|
||||||
|
2. Type barcode value or paste from clipboard
|
||||||
|
3. Press Enter to complete scan
|
||||||
|
4. Barcode logged to console
|
||||||
|
|
||||||
**Console Output Format:**
|
**Console Output Format:**
|
||||||
```
|
```
|
||||||
[Barcode Scanned] 14:07:32.456 | Barcode: 5901234123457 | Input: keyboard
|
[Barcode Scanned] 14:07:32.456 | Barcode: 5901234123457 | Input: keyboard
|
||||||
|
[Barcode Scanned] 14:07:45.123 | Barcode: 123456789012 | Input: camera
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Browser Support:**
|
||||||
|
- Chrome 53+, Firefox 55+, Safari 11+, Edge 79+
|
||||||
|
- Camera access requires HTTPS or localhost
|
||||||
|
- Mobile browsers support camera scanning
|
||||||
|
|
||||||
**Testing Instructions:**
|
**Testing Instructions:**
|
||||||
1. Navigate to the Barcode Scanner page via navbar
|
1. Navigate to the Barcode Scanner page via navbar
|
||||||
2. Type a barcode value or use a barcode scanner device
|
2. Try keyboard input mode: Type a barcode and press Enter
|
||||||
3. Press Enter to complete the scan
|
3. Try camera mode: Switch to "Camera Scan" and point at a barcode
|
||||||
4. Press F12 to open Developer Tools
|
4. Press F12 to open DevTools Console
|
||||||
5. Switch to the Console tab to view scanned barcodes with metadata
|
5. View logged barcodes with timestamps and input type
|
||||||
|
|
||||||
### 📦 Storage Location Pages
|
### 📦 Storage Location Pages
|
||||||
- `pantry.html` — Pantry inventory (expandable for displaying specific items)
|
- `pantry.html` — Pantry inventory (expandable for displaying specific items)
|
||||||
@@ -143,7 +166,7 @@ python -m http.server 8000
|
|||||||
The navbar appears at the top of every page and provides links to:
|
The navbar appears at the top of every page and provides links to:
|
||||||
- Homepage — Main demo page with pie chart
|
- Homepage — Main demo page with pie chart
|
||||||
- Search — Advanced inventory search and filtering with expiry date support
|
- Search — Advanced inventory search and filtering with expiry date support
|
||||||
- Barcode Scanner — Barcode capture interface (testing via console)
|
- Barcode Scanner — Barcode capture with camera and keyboard modes
|
||||||
- Pantry, Fridge, Freezer — Individual storage location pages
|
- Pantry, Fridge, Freezer — Individual storage location pages
|
||||||
|
|
||||||
### Using the Search Page
|
### Using the Search Page
|
||||||
@@ -152,7 +175,7 @@ The navbar appears at the top of every page and provides links to:
|
|||||||
- Item name (optional)
|
- Item name (optional)
|
||||||
- Storage location (optional, defaults to "All")
|
- Storage location (optional, defaults to "All")
|
||||||
- Quantity range (optional, defaults to 0-999)
|
- Quantity range (optional, defaults to 0-999)
|
||||||
- **Expiry date range (optional) — Leave blank to ignore**
|
- Expiry date range (optional) — Leave blank to ignore
|
||||||
3. Click "Search" or press Enter
|
3. Click "Search" or press Enter
|
||||||
4. Results display as item cards using the item component with expiry status badges
|
4. Results display as item cards using the item component with expiry status badges
|
||||||
5. Click "Reset" to clear all filters
|
5. Click "Reset" to clear all filters
|
||||||
@@ -163,14 +186,33 @@ The navbar appears at the top of every page and provides links to:
|
|||||||
- Find fresh items: Set start date to tomorrow, end date to 90 days from today
|
- Find fresh items: Set start date to tomorrow, end date to 90 days from today
|
||||||
|
|
||||||
### Using the Barcode Scanner
|
### Using the Barcode Scanner
|
||||||
|
|
||||||
|
#### Keyboard Mode
|
||||||
1. Open the Barcode Scanner page from the navbar
|
1. Open the Barcode Scanner page from the navbar
|
||||||
2. Click in the input field (auto-focused)
|
2. Make sure "Keyboard Input" mode is selected
|
||||||
3. Enter a barcode:
|
3. Click in the input field (auto-focused)
|
||||||
|
4. Enter a barcode:
|
||||||
- Type manually and press Enter
|
- Type manually and press Enter
|
||||||
- Scan with a barcode scanner device
|
- Scan with a barcode scanner device
|
||||||
- Paste a value (Ctrl/Cmd+V)
|
- Paste a value (Ctrl/Cmd+V)
|
||||||
4. Press F12 to open Developer Tools Console
|
5. Press F12 to open Developer Tools Console
|
||||||
5. View scanned barcodes in the Console tab with timestamp and metadata
|
6. View scanned barcodes in the Console tab with metadata
|
||||||
|
|
||||||
|
#### Camera Mode
|
||||||
|
1. Open the Barcode Scanner page from the navbar
|
||||||
|
2. Click "Camera Scan" to switch to camera mode
|
||||||
|
3. Click "Start Camera"
|
||||||
|
4. Browser will request camera permission (grant access)
|
||||||
|
5. Position barcode in front of camera lens
|
||||||
|
6. Barcode is detected automatically and displayed
|
||||||
|
7. Press F12 to view console logs
|
||||||
|
8. Click "Stop Camera" to end
|
||||||
|
|
||||||
|
**Tips:**
|
||||||
|
- Ensure good lighting for better barcode detection
|
||||||
|
- Hold barcode steady and clearly in frame
|
||||||
|
- Device must have a working camera
|
||||||
|
- Camera mode requires HTTPS or localhost
|
||||||
|
|
||||||
### The Item Component
|
### The Item Component
|
||||||
The `item-component` is a reusable UI building block used throughout the app. See `ITEM_COMPONENT.md` for detailed documentation on using it in your own pages.
|
The `item-component` is a reusable UI building block used throughout the app. See `ITEM_COMPONENT.md` for detailed documentation on using it in your own pages.
|
||||||
@@ -251,10 +293,34 @@ export const inventoryData = [
|
|||||||
- Implement backend storage (currently using static data)
|
- Implement backend storage (currently using static data)
|
||||||
- Add user authentication
|
- Add user authentication
|
||||||
- Integrate with real barcode/UPC database
|
- Integrate with real barcode/UPC database
|
||||||
|
- Deploy camera barcode detection to server (for performance optimization)
|
||||||
- Consider a frontend framework (React, Vue, etc.) for scale
|
- Consider a frontend framework (React, Vue, etc.) for scale
|
||||||
- Add unit/integration tests
|
- Add unit/integration tests
|
||||||
- Set up CI/CD pipeline
|
- Set up CI/CD pipeline
|
||||||
- Implement database for persistent storage and expiry date tracking
|
- Implement database for persistent storage and inventory tracking
|
||||||
|
|
||||||
|
## Technical Notes
|
||||||
|
|
||||||
|
### Barcode Scanning Library
|
||||||
|
- **Library:** Quagga2 (local version v1.12.1)
|
||||||
|
- **Location:** `./quagga2/quagga2-1.12.1/docs/examples/dist/quagga.min.js` (153 KB)
|
||||||
|
- **Why Quagga2:** Improved barcode detection accuracy and performance compared to original Quagga
|
||||||
|
- **Supported Formats:** UPC-A, UPC-E, EAN-13, EAN-8, Code-128, Code-39
|
||||||
|
- **API:** Backward compatible with original Quagga.js; no code changes needed
|
||||||
|
- **Initialization:** See `barcode.html` lines 268-290 for camera initialization logic
|
||||||
|
|
||||||
|
### Camera Access
|
||||||
|
- Requires browser permission (user must grant access)
|
||||||
|
- Works over HTTPS or localhost only (security requirement)
|
||||||
|
- Uses HTML5 MediaDevices API (`getUserMedia`)
|
||||||
|
- Video constraints: 800x600 resolution, environment-facing camera
|
||||||
|
- Real-time frame processing for barcode detection
|
||||||
|
|
||||||
|
### Performance Considerations
|
||||||
|
- Camera scanning is CPU-intensive on older devices
|
||||||
|
- Frame processing happens in real-time on main thread
|
||||||
|
- Consider WebWorkers for production optimization
|
||||||
|
- Network-free operation: all detection happens client-side
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
@@ -267,7 +333,8 @@ Contributions are welcome! Suggested improvements:
|
|||||||
- [ ] Implement item editing/deletion on storage pages
|
- [ ] Implement item editing/deletion on storage pages
|
||||||
- [ ] Add barcode/UPC lookup for real products
|
- [ ] Add barcode/UPC lookup for real products
|
||||||
- [ ] Convert item-component to custom element (`<item-component>`)
|
- [ ] Convert item-component to custom element (`<item-component>`)
|
||||||
- [ ] Add more detailed inventory tracking (expiration dates calculations, locations within rooms, batch numbers, etc.)
|
- [ ] Optimize camera barcode detection for performance
|
||||||
|
- [ ] Add more barcode format support
|
||||||
- [ ] Add email/notification alerts for items expiring soon
|
- [ ] Add email/notification alerts for items expiring soon
|
||||||
- [ ] Implement CSV import/export for inventory
|
- [ ] Implement CSV import/export for inventory
|
||||||
|
|
||||||
|
|||||||
@@ -145,3 +145,13 @@ export function getScanningStats() {
|
|||||||
message: 'Barcode scanner is active. Check console for scanned barcodes.'
|
message: 'Barcode scanner is active. Check console for scanned barcodes.'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log camera-detected barcode to console
|
||||||
|
* Camera detection is already included in logBarcodeToConsole with inputType = 'camera'
|
||||||
|
* This function maintains backward compatibility
|
||||||
|
* @param {string} barcode - The camera-detected barcode
|
||||||
|
*/
|
||||||
|
export function logCameraDetectedBarcode(barcode) {
|
||||||
|
logBarcodeToConsole(barcode, 'camera-device');
|
||||||
|
}
|
||||||
|
|||||||
407
barcode.html
@@ -5,12 +5,13 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="main.css">
|
<link rel="stylesheet" href="main.css">
|
||||||
<title>Barcode Scanner</title>
|
<title>Barcode Scanner</title>
|
||||||
|
<script src="./quagga2/quagga2-1.12.1/docs/examples/dist/quagga.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
.scanner-container {
|
.scanner-container {
|
||||||
background: #f9f9f9;
|
background: #f9f9f9;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
max-width: 600px;
|
max-width: 800px;
|
||||||
margin: 30px auto;
|
margin: 30px auto;
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
@@ -31,6 +32,64 @@
|
|||||||
margin: 10px 0 0 0;
|
margin: 10px 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mode-toggle {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: 2px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: white;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-btn.active {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border-color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-btn:hover:not(.active) {
|
||||||
|
border-color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-section {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-section.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-section.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#video {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #000;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.barcode-input-wrapper {
|
.barcode-input-wrapper {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
@@ -60,6 +119,11 @@
|
|||||||
background-color: #fffef0;
|
background-color: #fffef0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.barcode-input.highlight {
|
||||||
|
border-color: #4CAF50;
|
||||||
|
background-color: #e8f5e9;
|
||||||
|
}
|
||||||
|
|
||||||
.scanner-status {
|
.scanner-status {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
@@ -129,10 +193,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
flex: 1;
|
|
||||||
padding: 12px 20px;
|
padding: 12px 20px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -140,6 +204,17 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.3s;
|
transition: background-color 0.3s;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #45a049;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
@@ -151,6 +226,15 @@
|
|||||||
background-color: #bbb;
|
background-color: #bbb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
}
|
||||||
|
|
||||||
.open-console {
|
.open-console {
|
||||||
background-color: #2196F3;
|
background-color: #2196F3;
|
||||||
color: white;
|
color: white;
|
||||||
@@ -160,6 +244,41 @@
|
|||||||
background-color: #0b7dda;
|
background-color: #0b7dda;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
background-color: #ffebee;
|
||||||
|
border: 1px solid #f5a5ac;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 12px;
|
||||||
|
color: #c62828;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detected-barcode {
|
||||||
|
background-color: #e8f5e9;
|
||||||
|
border: 2px solid #4CAF50;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detected-barcode.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detected-barcode-value {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2e7d32;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive design */
|
/* Responsive design */
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.scanner-container {
|
.scanner-container {
|
||||||
@@ -179,6 +298,15 @@
|
|||||||
.btn {
|
.btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mode-toggle {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-btn {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -191,33 +319,82 @@
|
|||||||
<div class="scanner-container">
|
<div class="scanner-container">
|
||||||
<div class="scanner-header">
|
<div class="scanner-header">
|
||||||
<h3>📱 Barcode Scanner</h3>
|
<h3>📱 Barcode Scanner</h3>
|
||||||
<p>Scan a barcode or manually enter one below</p>
|
<p>Scan barcodes using camera or keyboard input</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="scanner-status active" id="scanner-status">
|
<!-- Mode Toggle -->
|
||||||
✓ Scanner Ready - Click below and scan a barcode
|
<div class="mode-toggle">
|
||||||
|
<button type="button" class="mode-btn active" id="keyboard-mode-btn">
|
||||||
|
⌨️ Keyboard Input
|
||||||
|
</button>
|
||||||
|
<button type="button" class="mode-btn" id="camera-mode-btn">
|
||||||
|
📷 Camera Scan
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="barcode-input-wrapper">
|
<!-- Error Message -->
|
||||||
<label for="barcode-input">Barcode Input:</label>
|
<div class="error-message" id="error-message"></div>
|
||||||
<input
|
|
||||||
type="text"
|
<!-- Keyboard Input Section -->
|
||||||
id="barcode-input"
|
<div class="keyboard-section" id="keyboard-section">
|
||||||
class="barcode-input"
|
<div class="scanner-status active" id="scanner-status">
|
||||||
placeholder="Scan or type barcode here..."
|
✓ Scanner Ready - Click below and scan a barcode
|
||||||
autocomplete="off"
|
</div>
|
||||||
spellcheck="false"
|
|
||||||
>
|
<div class="barcode-input-wrapper">
|
||||||
|
<label for="barcode-input">Barcode Input:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="barcode-input"
|
||||||
|
class="barcode-input"
|
||||||
|
placeholder="Scan or type barcode here..."
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scanner-info">
|
||||||
|
<h4>How to Use (Keyboard):</h4>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Keyboard Entry:</strong> Type or paste a barcode and press Enter</li>
|
||||||
|
<li><strong>Hardware Scanner:</strong> Plug in a barcode scanner and scan directly</li>
|
||||||
|
<li><strong>Testing:</strong> Scanned barcodes are logged to the browser console (Press F12)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="scanner-info">
|
<!-- Camera Section -->
|
||||||
<h4>How to Use:</h4>
|
<div class="camera-section" id="camera-section">
|
||||||
<ul>
|
<div class="scanner-status" id="camera-status">
|
||||||
<li><strong>Keyboard Entry:</strong> Type or paste a barcode and press Enter</li>
|
📷 Camera Ready - Click "Start Camera" to begin scanning
|
||||||
<li><strong>Hardware Scanner:</strong> Plug in a barcode scanner and scan directly</li>
|
</div>
|
||||||
<li><strong>Testing:</strong> Scanned barcodes are logged to the browser console (Press F12)</li>
|
|
||||||
<li><strong>Input Focus:</strong> The input field automatically refocuses after each scan</li>
|
<video id="video" width="800" height="600"></video>
|
||||||
</ul>
|
|
||||||
|
<div class="camera-controls">
|
||||||
|
<button type="button" class="btn btn-primary" id="start-camera-btn">
|
||||||
|
▶️ Start Camera
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-danger" id="stop-camera-btn" style="display: none;">
|
||||||
|
⏹️ Stop Camera
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="detected-barcode-display" class="detected-barcode">
|
||||||
|
<div style="font-size: 12px; color: #666; margin-bottom: 5px;">Detected Barcode:</div>
|
||||||
|
<div class="detected-barcode-value" id="detected-barcode-value">-</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scanner-info">
|
||||||
|
<h4>How to Use (Camera):</h4>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Permission:</strong> Browser will request camera access on first use</li>
|
||||||
|
<li><strong>Position:</strong> Hold barcode in front of camera lens</li>
|
||||||
|
<li><strong>Detection:</strong> Barcode is detected automatically in real-time</li>
|
||||||
|
<li><strong>Supported:</strong> UPC, EAN, Code-128, Code-39, and more</li>
|
||||||
|
<li><strong>Console:</strong> All detections logged to console (Press F12)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="console-hint">
|
<div class="console-hint">
|
||||||
@@ -240,31 +417,183 @@
|
|||||||
<script type="module">
|
<script type="module">
|
||||||
import { initializeBarcodeScanner, logBarcodeToConsole } from './barcode-scanner.js';
|
import { initializeBarcodeScanner, logBarcodeToConsole } from './barcode-scanner.js';
|
||||||
|
|
||||||
|
const keyboardModeBtn = document.getElementById('keyboard-mode-btn');
|
||||||
|
const cameraModeBtn = document.getElementById('camera-mode-btn');
|
||||||
|
const keyboardSection = document.getElementById('keyboard-section');
|
||||||
|
const cameraSection = document.getElementById('camera-section');
|
||||||
const barcodeInput = document.getElementById('barcode-input');
|
const barcodeInput = document.getElementById('barcode-input');
|
||||||
const scannerStatus = document.getElementById('scanner-status');
|
const scannerStatus = document.getElementById('scanner-status');
|
||||||
|
const cameraStatus = document.getElementById('camera-status');
|
||||||
|
const video = document.getElementById('video');
|
||||||
|
const startCameraBtn = document.getElementById('start-camera-btn');
|
||||||
|
const stopCameraBtn = document.getElementById('stop-camera-btn');
|
||||||
const openConsoleBtn = document.getElementById('open-console-btn');
|
const openConsoleBtn = document.getElementById('open-console-btn');
|
||||||
const clearInputBtn = document.getElementById('clear-input-btn');
|
const clearInputBtn = document.getElementById('clear-input-btn');
|
||||||
|
const errorMessage = document.getElementById('error-message');
|
||||||
|
const detectedBarcodeDisplay = document.getElementById('detected-barcode-display');
|
||||||
|
const detectedBarcodeValue = document.getElementById('detected-barcode-value');
|
||||||
|
|
||||||
|
let cameraActive = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback when a barcode is scanned
|
* Callback when a barcode is scanned
|
||||||
* @param {string} barcode - The scanned barcode value
|
|
||||||
* @param {string} inputType - The type of input device
|
|
||||||
*/
|
*/
|
||||||
function onBarcodeScanned(barcode, inputType) {
|
function onBarcodeScanned(barcode, inputType) {
|
||||||
// Update status temporarily
|
if (inputType === 'camera') {
|
||||||
const originalStatus = scannerStatus.textContent;
|
// Camera detection feedback
|
||||||
scannerStatus.textContent = `✓ Barcode scanned: ${barcode}`;
|
detectedBarcodeValue.textContent = barcode;
|
||||||
scannerStatus.style.backgroundColor = '#a5d6a7';
|
detectedBarcodeDisplay.classList.add('show');
|
||||||
|
cameraStatus.textContent = `✓ Barcode detected: ${barcode}`;
|
||||||
// Reset after 2 seconds
|
cameraStatus.style.backgroundColor = '#a5d6a7';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
scannerStatus.textContent = originalStatus;
|
cameraStatus.textContent = '📷 Scanning...';
|
||||||
scannerStatus.style.backgroundColor = '';
|
cameraStatus.style.backgroundColor = '';
|
||||||
}, 2000);
|
detectedBarcodeDisplay.classList.remove('show');
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
// Keyboard detection feedback
|
||||||
|
const originalStatus = scannerStatus.textContent;
|
||||||
|
scannerStatus.textContent = `✓ Barcode scanned: ${barcode}`;
|
||||||
|
scannerStatus.style.backgroundColor = '#a5d6a7';
|
||||||
|
setTimeout(() => {
|
||||||
|
scannerStatus.textContent = originalStatus;
|
||||||
|
scannerStatus.style.backgroundColor = '';
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the barcode scanner
|
/**
|
||||||
initializeBarcodeScanner(barcodeInput, onBarcodeScanned);
|
* Switch to keyboard mode
|
||||||
|
*/
|
||||||
|
function switchToKeyboardMode() {
|
||||||
|
keyboardModeBtn.classList.add('active');
|
||||||
|
cameraModeBtn.classList.remove('active');
|
||||||
|
keyboardSection.classList.remove('hidden');
|
||||||
|
cameraSection.classList.remove('active');
|
||||||
|
|
||||||
|
if (cameraActive) {
|
||||||
|
stopCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
barcodeInput.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch to camera mode
|
||||||
|
*/
|
||||||
|
function switchToCameraMode() {
|
||||||
|
cameraModeBtn.classList.add('active');
|
||||||
|
keyboardModeBtn.classList.remove('active');
|
||||||
|
cameraSection.classList.add('active');
|
||||||
|
keyboardSection.classList.add('hidden');
|
||||||
|
errorMessage.classList.remove('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start camera and barcode detection
|
||||||
|
*/
|
||||||
|
async function startCamera() {
|
||||||
|
try {
|
||||||
|
cameraActive = true;
|
||||||
|
startCameraBtn.style.display = 'none';
|
||||||
|
stopCameraBtn.style.display = 'block';
|
||||||
|
cameraStatus.textContent = '📷 Scanning...';
|
||||||
|
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
|
video: { facingMode: 'environment', width: { ideal: 800 }, height: { ideal: 600 } }
|
||||||
|
});
|
||||||
|
|
||||||
|
video.srcObject = stream;
|
||||||
|
video.setAttribute('playsinline', 'true');
|
||||||
|
|
||||||
|
// Wait for video to load
|
||||||
|
await new Promise(resolve => {
|
||||||
|
video.onloadedmetadata = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
video.play();
|
||||||
|
|
||||||
|
// Initialize Quagga barcode detection
|
||||||
|
Quagga.init({
|
||||||
|
inputStream: {
|
||||||
|
name: 'Live',
|
||||||
|
type: 'LiveStream',
|
||||||
|
target: '#video',
|
||||||
|
constraints: { width: 800, height: 600, facingMode: 'environment' }
|
||||||
|
},
|
||||||
|
decoder: {
|
||||||
|
readers: ['ean_reader', 'ean_8_reader', 'upc_reader', 'upc_e_reader', 'code_128_reader', 'code_39_reader']
|
||||||
|
}
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
showError('Camera initialization error: ' + err.message);
|
||||||
|
stopCamera();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Quagga.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle barcode detection
|
||||||
|
Quagga.onDetected(function(result) {
|
||||||
|
if (result.codeResult) {
|
||||||
|
const barcode = result.codeResult.code;
|
||||||
|
logBarcodeToConsole(barcode, 'camera');
|
||||||
|
onBarcodeScanned(barcode, 'camera');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
showError('Camera access denied or not available: ' + error.message);
|
||||||
|
stopCamera();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop camera and barcode detection
|
||||||
|
*/
|
||||||
|
function stopCamera() {
|
||||||
|
cameraActive = false;
|
||||||
|
startCameraBtn.style.display = 'block';
|
||||||
|
stopCameraBtn.style.display = 'none';
|
||||||
|
cameraStatus.textContent = '📷 Camera stopped';
|
||||||
|
|
||||||
|
if (Quagga.initialized) {
|
||||||
|
Quagga.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video.srcObject) {
|
||||||
|
video.srcObject.getTracks().forEach(track => track.stop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show error message
|
||||||
|
*/
|
||||||
|
function showError(message) {
|
||||||
|
errorMessage.textContent = '❌ ' + message;
|
||||||
|
errorMessage.classList.add('show');
|
||||||
|
setTimeout(() => {
|
||||||
|
errorMessage.classList.remove('show');
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keyboard input callback
|
||||||
|
*/
|
||||||
|
function onKeyboardBarcodeScanned(barcode, inputType) {
|
||||||
|
onBarcodeScanned(barcode, inputType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize keyboard barcode scanner
|
||||||
|
initializeBarcodeScanner(barcodeInput, onKeyboardBarcodeScanned);
|
||||||
|
|
||||||
|
// Mode toggle event listeners
|
||||||
|
keyboardModeBtn.addEventListener('click', switchToKeyboardMode);
|
||||||
|
cameraModeBtn.addEventListener('click', switchToCameraMode);
|
||||||
|
|
||||||
|
// Camera control event listeners
|
||||||
|
startCameraBtn.addEventListener('click', startCamera);
|
||||||
|
stopCameraBtn.addEventListener('click', stopCamera);
|
||||||
|
|
||||||
// Clear input button
|
// Clear input button
|
||||||
clearInputBtn.addEventListener('click', () => {
|
clearInputBtn.addEventListener('click', () => {
|
||||||
@@ -272,11 +601,9 @@
|
|||||||
barcodeInput.focus();
|
barcodeInput.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Open console button - opens DevTools
|
// Open console button
|
||||||
openConsoleBtn.addEventListener('click', () => {
|
openConsoleBtn.addEventListener('click', () => {
|
||||||
console.log('%c📊 Barcode Scanner Console\n\nUse this console to view scanned barcodes and their details.\nStart scanning to see logs appear here.', 'color: #4CAF50; font-size: 14px; font-weight: bold; line-height: 1.8;');
|
console.log('%c📊 Barcode Scanner Console\n\nUse this console to view scanned barcodes and their details.\nStart scanning to see logs appear here.', 'color: #4CAF50; font-size: 14px; font-weight: bold; line-height: 1.8;');
|
||||||
// In a real app, we can't programmatically open DevTools for security reasons
|
|
||||||
// But we can log a helpful message and suggest the user press F12
|
|
||||||
alert('Press F12 (or Cmd+Option+I on Mac) to open the Developer Tools Console.\n\nScanned barcodes will appear in the Console tab.');
|
alert('Press F12 (or Cmd+Option+I on Mac) to open the Developer Tools Console.\n\nScanned barcodes will appear in the Console tab.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
26
quagga2/quagga2-1.12.1/.babelrc
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
["@babel/preset-env", { "useBuiltIns": "entry", "corejs": { "version": 3, "proposals": true } }],
|
||||||
|
"@babel/preset-typescript"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"@babel/plugin-proposal-class-properties",
|
||||||
|
"@babel/plugin-proposal-object-rest-spread",
|
||||||
|
"@babel/plugin-transform-runtime",
|
||||||
|
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||||
|
"@babel/plugin-proposal-optional-chaining"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"node": {
|
||||||
|
"plugins": [
|
||||||
|
"add-module-exports"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"plugins": [
|
||||||
|
"add-module-exports",
|
||||||
|
"istanbul"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
quagga2/quagga2-1.12.1/.eslintrc
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"airbnb-base",
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
// "plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||||
|
"airbnb-typescript-base"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2019,
|
||||||
|
"sourceType": "module",
|
||||||
|
"project": "./tsconfig.json"
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"node": true,
|
||||||
|
"es6": true
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"ENV": "readonly"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"camelcase": "off",
|
||||||
|
"comma-dangle": [ "error", "always-multiline" ],
|
||||||
|
"curly": "error",
|
||||||
|
"eqeqeq": "error",
|
||||||
|
"guard-for-in": "error",
|
||||||
|
"new-cap": "error",
|
||||||
|
"no-caller": "error",
|
||||||
|
"no-empty": "warn",
|
||||||
|
"no-extra-boolean-cast": "warn",
|
||||||
|
"no-multi-spaces": "error",
|
||||||
|
"no-new": "error",
|
||||||
|
"no-plusplus": "off",
|
||||||
|
"no-trailing-spaces": "error",
|
||||||
|
"no-underscore-dangle": "off",
|
||||||
|
"no-unused-expressions": "error",
|
||||||
|
"no-use-before-define": "warn",
|
||||||
|
"quotes": [ "error", "single" ],
|
||||||
|
"strict": "off",
|
||||||
|
"wrap-iife": [ "error", "outside" ],
|
||||||
|
"import/no-named-as-default": "off",
|
||||||
|
"@typescript-eslint/restrict-template-expressions": "off",
|
||||||
|
"@typescript-eslint/prefer-namespace-keyword": "off",
|
||||||
|
// STYLE
|
||||||
|
"brace-style": ["error", "1tbs"],
|
||||||
|
"comma-spacing": ["error", { "before": false, "after": true }],
|
||||||
|
"comma-style": ["error", "last"],
|
||||||
|
"consistent-return": "error",
|
||||||
|
"consistent-this": ["warn", "self"],
|
||||||
|
"dot-notation": "error",
|
||||||
|
"eol-last": "error",
|
||||||
|
"func-call-spacing": [ "error", "never" ],
|
||||||
|
"indent": ["error", 4, { "SwitchCase": 1 }],
|
||||||
|
"@typescript-eslint/indent": ["error", 4],
|
||||||
|
"key-spacing": ["warn", { "beforeColon": false, "afterColon": true }],
|
||||||
|
"keyword-spacing": [ "error", { "after": true }],
|
||||||
|
"max-len" : ["warn", 120],
|
||||||
|
"max-params": ["error", 7],
|
||||||
|
"new-parens": "error",
|
||||||
|
"no-array-constructor": "error",
|
||||||
|
"no-mixed-spaces-and-tabs": "error",
|
||||||
|
"no-multiple-empty-lines": "error",
|
||||||
|
"no-shadow": "error",
|
||||||
|
"no-undef": "error",
|
||||||
|
"padded-blocks": ["error", "never"],
|
||||||
|
"semi-spacing": "error",
|
||||||
|
"semi": ["error", "always"],
|
||||||
|
"space-infix-ops": "error",
|
||||||
|
"yoda": "error",
|
||||||
|
"linebreak-style": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
11
quagga2/quagga2-1.12.1/.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.sass-cache/
|
||||||
|
node_modules/
|
||||||
|
coverage/
|
||||||
|
.project
|
||||||
|
_site/
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
dist/
|
||||||
|
lib/quagga.js
|
||||||
|
cypress/videos
|
||||||
|
.nyc_output
|
||||||
18
quagga2/quagga2-1.12.1/.ncurc.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"upgrade": true,
|
||||||
|
"reject": [
|
||||||
|
"@cypress/code-coverage",
|
||||||
|
"@cypress/webpack-preprocessor",
|
||||||
|
"babel-loader",
|
||||||
|
"chai",
|
||||||
|
"cypress",
|
||||||
|
"eslint",
|
||||||
|
"mocha",
|
||||||
|
"sinon-chai",
|
||||||
|
"source-map-loader",
|
||||||
|
"@types/mocha",
|
||||||
|
"@types/sinon-chai",
|
||||||
|
"webpack-cli",
|
||||||
|
"webpack"
|
||||||
|
]
|
||||||
|
}
|
||||||
37
quagga2/quagga2-1.12.1/.npmignore
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
cypress/
|
||||||
|
test/
|
||||||
|
example/
|
||||||
|
examples/
|
||||||
|
.sass-cache/
|
||||||
|
node_modules/
|
||||||
|
coverage/
|
||||||
|
.project
|
||||||
|
_site/
|
||||||
|
_includes/
|
||||||
|
.idea/
|
||||||
|
doc/
|
||||||
|
src/
|
||||||
|
.gitignore
|
||||||
|
*.BACKUP.*
|
||||||
|
*.BASE.*
|
||||||
|
*.LOCAL.*
|
||||||
|
*.REMOTE.*
|
||||||
|
.babelrc
|
||||||
|
.eslintrc
|
||||||
|
server.pem
|
||||||
|
simple-https-server.py
|
||||||
|
webpack.*config*.js
|
||||||
|
.travis.yml
|
||||||
|
build/
|
||||||
|
*.tgz
|
||||||
|
.snyk
|
||||||
|
configs/*
|
||||||
|
.github/*
|
||||||
|
CHANGELOG.md
|
||||||
|
Dockerfile
|
||||||
|
README.md
|
||||||
|
.ncurc.json
|
||||||
|
.nyc_output/
|
||||||
|
.vscode/
|
||||||
|
cypress.json
|
||||||
|
docker-compose.yml
|
||||||
4
quagga2/quagga2-1.12.1/.snyk
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
|
||||||
|
version: v1.13.5
|
||||||
|
ignore: {}
|
||||||
|
patch: {}
|
||||||
338
quagga2/quagga2-1.12.1/DEPENDENCIES.md
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
# 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
|
||||||
|
- **Location**: Listed in `dependencies` (not `devDependencies`) - see [Type Definition Dependencies](#type-definition-dependencies) below
|
||||||
|
- **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
|
||||||
|
- `type-definitions/quagga.d.ts` - vec2 type import for `Moment.vec` property
|
||||||
|
|
||||||
|
- **`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.17.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
|
||||||
|
|
||||||
|
- **`ndarray-pixels`** (^5.0.1)
|
||||||
|
- **Platform**: Node.js only
|
||||||
|
- **Purpose**: Image decoding for `decodeSingle` in Node.js
|
||||||
|
- **Usage**: Required for Node.js `decodeSingle()` support; install with `npm install ndarray-pixels sharp`
|
||||||
|
- **Note**: Marked external in Node webpack build; not bundled into `lib/quagga.js`
|
||||||
|
|
||||||
|
- **`sharp`** (^0.34.0)
|
||||||
|
- **Platform**: Node.js only
|
||||||
|
- **Purpose**: Native image processing (used by `ndarray-pixels` on Node.js)
|
||||||
|
- **Usage**: Required for Node.js `decodeSingle()` support; transitive dependency of `ndarray-pixels`
|
||||||
|
- **Note**: Marked external in Node webpack build; not bundled into `lib/quagga.js`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overrides
|
||||||
|
|
||||||
|
No package overrides are currently needed. Cypress 13.17.0+ uses `@cypress/request@^3.0.6` which includes the security fix for the `form-data` vulnerability (versions >= 3.0.6 use `form-data@~4.0.0` which is safe).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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.17.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
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Type Definition Dependencies
|
||||||
|
|
||||||
|
### Exception: `gl-matrix` in `dependencies`
|
||||||
|
|
||||||
|
While most packages in Quagga2 are listed in `devDependencies` because they are bundled (see [Background](#background)), **`gl-matrix` is an exception** and is listed in `dependencies`.
|
||||||
|
|
||||||
|
**Why?**
|
||||||
|
|
||||||
|
The TypeScript type definition file (`type-definitions/quagga.d.ts`) imports `vec2` from `gl-matrix`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
```
|
||||||
|
|
||||||
|
This type is used in the `Moment` interface:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export type Moment = {
|
||||||
|
// ... other properties
|
||||||
|
vec?: vec2;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
When TypeScript consumers use Quagga2 with type checking, the TypeScript compiler needs to resolve this import to provide proper type information. If `gl-matrix` were in `devDependencies`, it would not be installed when consumers run `npm install @ericblade/quagga2`, causing TypeScript compilation errors.
|
||||||
|
|
||||||
|
**Key distinction:**
|
||||||
|
|
||||||
|
- **Runtime bundling**: `gl-matrix` code IS bundled into `dist/quagga.min.js` - consumers don't need it at runtime
|
||||||
|
- **Type resolution**: TypeScript consumers DO need `gl-matrix` installed to resolve types in `quagga.d.ts`
|
||||||
|
|
||||||
|
This is a special case where type definitions create a true development-time dependency for consumers using TypeScript.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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: Why is `gl-matrix` in `dependencies` if everything else is in `devDependencies`?**
|
||||||
|
|
||||||
|
A: This is an exception for TypeScript type resolution. The type definition file (`type-definitions/quagga.d.ts`) imports `vec2` from `gl-matrix` to provide proper typing for the `Moment.vec` property. TypeScript consumers need `gl-matrix` installed to resolve these types during compilation. See [Type Definition Dependencies](#type-definition-dependencies) for details.
|
||||||
|
|
||||||
|
**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-12-01
|
||||||
7
quagga2/quagga2-1.12.1/Dockerfile
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
ARG NODE_VERSION=13
|
||||||
|
|
||||||
|
FROM node:${NODE_VERSION}-alpine
|
||||||
|
|
||||||
|
WORKDIR /quagga2
|
||||||
|
|
||||||
|
USER node
|
||||||
178
quagga2/quagga2-1.12.1/HALFSAMPLE_PERFORMANCE_ANALYSIS.md
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
# HalfSample Performance and Accuracy Analysis
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document summarizes the results of comprehensive testing of all Quagga2 decoders with both `halfSample: true` and `halfSample: false` configurations.
|
||||||
|
|
||||||
|
## Test Results Summary
|
||||||
|
|
||||||
|
**Total Tests**: 283 passing, 38 pending, 26 failing
|
||||||
|
**Test Duration**: ~24 seconds
|
||||||
|
|
||||||
|
All 13 decoder types were tested with both halfSample configurations:
|
||||||
|
- ean
|
||||||
|
- ean_extended
|
||||||
|
- code_128
|
||||||
|
- code_128_external (external reader)
|
||||||
|
- code_39
|
||||||
|
- code_39_vin
|
||||||
|
- code_32
|
||||||
|
- ean_8
|
||||||
|
- upc
|
||||||
|
- upc_e
|
||||||
|
- codabar
|
||||||
|
- i2of5
|
||||||
|
- 2of5
|
||||||
|
- code_93
|
||||||
|
|
||||||
|
## Performance Comparison
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
**Key Finding**: `halfSample: true` is consistently faster across ALL decoders, with performance improvements ranging from 13% to 50%.
|
||||||
|
|
||||||
|
| Decoder | halfSample: true Avg | halfSample: false Avg | Performance Gain | Tests Passed (both) |
|
||||||
|
|---------|---------------------|----------------------|------------------|---------------------|
|
||||||
|
| ean | 38.20ms | 48.90ms | 21.9% faster | 10/10 |
|
||||||
|
| ean_extended | 51.80ms | 87.10ms | **40.5% faster** | 10/10 |
|
||||||
|
| code_128 | 43.00ms | 68.90ms | **37.6% faster** | 10/10 |
|
||||||
|
| code_128_external | 41.60ms | 68.20ms | 39.0% faster | 10/10 |
|
||||||
|
| code_39 | 50.90ms | 60.10ms | 15.3% faster | 10/10 |
|
||||||
|
| code_39_vin | 91.27ms | 172.27ms | **47.0% faster** | 11/11 |
|
||||||
|
| code_32 | 351.20ms | 492.10ms | 28.6% faster | 10/10 |
|
||||||
|
| ean_8 | 32.38ms | 49.50ms | 34.6% faster | 8/8 |
|
||||||
|
| upc | 35.80ms | 44.40ms | 19.4% faster | 10/10 |
|
||||||
|
| upc_e | 32.30ms | 46.90ms | 31.1% faster | 10/10 |
|
||||||
|
| codabar | 34.20ms | 50.40ms | 32.1% faster | 10/10 |
|
||||||
|
| i2of5 | 54.40ms | 64.00ms | 15.0% faster | 5/5 |
|
||||||
|
| 2of5 | 51.10ms | 69.40ms | 26.4% faster | 10/10 |
|
||||||
|
| code_93 | 48.73ms | 63.82ms | 23.6% faster | 11/11 |
|
||||||
|
|
||||||
|
### Performance Insights
|
||||||
|
|
||||||
|
1. **Biggest Performance Gains** (>40% faster):
|
||||||
|
- `code_39_vin`: 47.0% faster with halfSample: true
|
||||||
|
- `ean_extended`: 40.5% faster with halfSample: true
|
||||||
|
- `code_128`: 37.6% faster with halfSample: true
|
||||||
|
|
||||||
|
2. **Moderate Performance Gains** (25-40% faster):
|
||||||
|
- `code_128_external`: 39.0% faster
|
||||||
|
- `ean_8`: 34.6% faster
|
||||||
|
- `codabar`: 32.1% faster
|
||||||
|
- `upc_e`: 31.1% faster
|
||||||
|
- `code_32`: 28.6% faster
|
||||||
|
- `2of5`: 26.4% faster
|
||||||
|
- `code_93`: 23.6% faster
|
||||||
|
|
||||||
|
3. **Smaller Performance Gains** (<25% faster):
|
||||||
|
- `ean`: 21.9% faster
|
||||||
|
- `upc`: 19.4% faster
|
||||||
|
- `code_39`: 15.3% faster
|
||||||
|
- `i2of5`: 15.0% faster
|
||||||
|
|
||||||
|
## Accuracy Comparison
|
||||||
|
|
||||||
|
### Failing Tests Analysis
|
||||||
|
|
||||||
|
**26 total failures** across both configurations, distributed as follows:
|
||||||
|
|
||||||
|
#### halfSample: false failures (more failures):
|
||||||
|
- Some decoders show reduced accuracy with halfSample: false
|
||||||
|
- Examples:
|
||||||
|
- `ean_extended` with halfSample: false had several failures (images 001, 003, 005, 006)
|
||||||
|
- `ean_8` with halfSample: false had incorrect decoding (image-009: got '57790770' instead of '42242215')
|
||||||
|
- `code_93` with halfSample: false had multiple failures (images 003, 004, 005, 007, 008, 010)
|
||||||
|
- `2of5` with halfSample: false had failures (images 005, 006)
|
||||||
|
|
||||||
|
#### halfSample: true failures:
|
||||||
|
- `code_128` with halfSample: true had one failure (image-004)
|
||||||
|
- `i2of5` with halfSample: true had failures (images 002, 004, 005)
|
||||||
|
|
||||||
|
### Accuracy Insights
|
||||||
|
|
||||||
|
1. **halfSample: true generally maintains or improves accuracy** while providing significant performance gains
|
||||||
|
2. Some specific images fail with halfSample: false that pass with halfSample: true
|
||||||
|
3. A few edge cases exist where halfSample: true may have issues (e.g., i2of5, code_128)
|
||||||
|
|
||||||
|
## Decoder-Specific Recommendations
|
||||||
|
|
||||||
|
Based on test results, here are recommendations for each decoder:
|
||||||
|
|
||||||
|
### Decoders that work BEST with halfSample: true
|
||||||
|
|
||||||
|
These decoders show excellent performance and accuracy with halfSample: true:
|
||||||
|
|
||||||
|
- **ean** - 21.9% faster, no new failures
|
||||||
|
- **ean_extended** - 40.5% faster, significantly better accuracy (4 images fail with halfSample: false)
|
||||||
|
- **code_39** - 15.3% faster, no new failures
|
||||||
|
- **code_39_vin** - 47.0% faster, no new failures
|
||||||
|
- **code_32** - 28.6% faster, no new failures
|
||||||
|
- **ean_8** - 34.6% faster, better accuracy (1 image fails with halfSample: false)
|
||||||
|
- **upc** - 19.4% faster, better accuracy (2 images fail with halfSample: false)
|
||||||
|
- **upc_e** - 31.1% faster, significantly better accuracy (6 images fail with halfSample: false)
|
||||||
|
- **codabar** - 32.1% faster, no new failures
|
||||||
|
- **2of5** - 26.4% faster, better accuracy (2 images fail with halfSample: false)
|
||||||
|
- **code_93** - 23.6% faster, significantly better accuracy (6 images fail with halfSample: false)
|
||||||
|
|
||||||
|
### Decoders with minor issues on halfSample: true
|
||||||
|
|
||||||
|
- **code_128** - 37.6% faster, but 2 images fail with halfSample: true (images 003, 004)
|
||||||
|
- **Recommendation**: Use halfSample: true for best performance, but be aware of potential edge cases
|
||||||
|
|
||||||
|
- **i2of5** - 15.0% faster, but 4 images fail with halfSample: true (images 001, 002, 004, 005)
|
||||||
|
- **Recommendation**: Consider halfSample: false if accuracy is critical, otherwise use halfSample: true for performance
|
||||||
|
|
||||||
|
### Summary by Use Case
|
||||||
|
|
||||||
|
**For maximum performance** (15-47% speed gains):
|
||||||
|
- Use `halfSample: true` for all decoders
|
||||||
|
- Accept minor accuracy tradeoffs for code_128 and i2of5
|
||||||
|
|
||||||
|
**For maximum accuracy**:
|
||||||
|
- Use `halfSample: true` for: ean, ean_extended, code_39, code_39_vin, code_32, ean_8, upc, upc_e, codabar, 2of5, code_93
|
||||||
|
- Consider `halfSample: false` only for i2of5 if accuracy is absolutely critical
|
||||||
|
|
||||||
|
**Balanced (RECOMMENDED)**:
|
||||||
|
- Use `halfSample: true` as default for all decoders
|
||||||
|
- Only switch to `halfSample: false` if you encounter specific accuracy issues in production
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
|
||||||
|
### When to use halfSample: true (RECOMMENDED DEFAULT)
|
||||||
|
|
||||||
|
✅ **Use halfSample: true for:**
|
||||||
|
- **Performance-critical applications** - provides 15-47% speed improvement
|
||||||
|
- **Most barcode types** - works reliably across all tested decoders
|
||||||
|
- **General use cases** - better balance of speed and accuracy
|
||||||
|
|
||||||
|
### When to consider halfSample: false
|
||||||
|
|
||||||
|
⚠️ **Consider halfSample: false only if:**
|
||||||
|
- Working specifically with i2of5 barcodes where accuracy is critical
|
||||||
|
- Specific accuracy issues are observed with halfSample: true for your use case
|
||||||
|
- You have very high-resolution images and can afford the performance penalty
|
||||||
|
- You're working with edge cases similar to the failing tests
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
**The data strongly supports using `halfSample: true` as the default configuration** for Quagga2. It provides:
|
||||||
|
|
||||||
|
1. **Consistent performance improvements** across all decoder types (15-47% faster)
|
||||||
|
2. **Equal or better accuracy** in 11 out of 13 decoder types
|
||||||
|
3. **Significant speed gains** for complex decoders like code_39_vin (47%), ean_extended (40.5%), and code_128 (37.6%)
|
||||||
|
|
||||||
|
The only exceptions are:
|
||||||
|
- **code_128**: 2 edge case failures with halfSample: true (but much faster)
|
||||||
|
- **i2of5**: 4 test failures with halfSample: true (consider halfSample: false if accuracy is critical)
|
||||||
|
|
||||||
|
For the vast majority of use cases, halfSample: true provides the best balance of performance and accuracy.
|
||||||
|
|
||||||
|
## Implementation Changes
|
||||||
|
|
||||||
|
The test suite has been enhanced to:
|
||||||
|
- Test all decoders with both halfSample: true and false
|
||||||
|
- Collect timing data for performance analysis
|
||||||
|
- Provide clear labeling of which configuration is being tested
|
||||||
|
- Generate performance summaries after test execution
|
||||||
|
|
||||||
|
This ensures ongoing monitoring of halfSample impact on both performance and accuracy.
|
||||||
21
quagga2/quagga2-1.12.1/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Christoph Oberhofer, (c) 2019 Eric Blade and all other contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
997
quagga2/quagga2-1.12.1/README.md
Normal file
@@ -0,0 +1,997 @@
|
|||||||
|
# quagga2
|
||||||
|
|
||||||
|
[](https://rollingversions.com/ericblade/quagga2)
|
||||||
|
|
||||||
|
[](https://gitter.im/quaggaJS/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
[](https://openbase.io/js/@ericblade/quagga2?utm_source=embedded&utm_medium=badge&utm_campaign=rate-badge)
|
||||||
|
|
||||||
|
This is a fork of the original QuaggaJS library, that will be maintained until such time as the
|
||||||
|
original author and maintainer returns, or it has been completely replaced by built-in browser and
|
||||||
|
node functionality.
|
||||||
|
|
||||||
|
## 📚 Documentation
|
||||||
|
|
||||||
|
**[Complete Documentation](https://ericblade.github.io/quagga2/)** - Tutorials, guides, API reference, and more
|
||||||
|
(UNDER CONSTRUCTION!!!)
|
||||||
|
|
||||||
|
Quick links from this README:
|
||||||
|
|
||||||
|
- [Changelog](https://github.com/ericblade/quagga2/releases)
|
||||||
|
- [Browser Support](#browser-support)
|
||||||
|
- [Installing](#installing)
|
||||||
|
- [Getting Started](#gettingstarted)
|
||||||
|
- [Using with React](#usingwithreact)
|
||||||
|
- [Using External Readers](#usingwithexternalreaders)
|
||||||
|
- [API](#api)
|
||||||
|
- [CameraAccess API](#cameraaccess-api)
|
||||||
|
- [Configuration](#configobject)
|
||||||
|
- [Tips & Tricks](#tipsandtricks)
|
||||||
|
|
||||||
|
## Using React / Redux?
|
||||||
|
|
||||||
|
Please see also <https://github.com/ericblade/quagga2-react-example/> and <https://github.com/ericblade/quagga2-redux-middleware/>. For live browser examples, see [docs/examples/](docs/examples/).
|
||||||
|
|
||||||
|
## Using Angular?
|
||||||
|
|
||||||
|
Please see <https://github.com/julienboulay/ngx-barcode-scanner> or <https://github.com/classycodeoss/mobile-scanning-demo>
|
||||||
|
|
||||||
|
## Using ThingWorx?
|
||||||
|
|
||||||
|
Please see <https://github.com/ptc-iot-sharing/ThingworxBarcodeScannerWidget>
|
||||||
|
|
||||||
|
## Using Vue?
|
||||||
|
|
||||||
|
- **Vue 2**: <https://github.com/DevinNorgarb/vue-quagga-2>
|
||||||
|
- **Vue 3**: <https://github.com/nick-0101/vue3-quagga-2>
|
||||||
|
|
||||||
|
## What is QuaggaJS?
|
||||||
|
|
||||||
|
QuaggaJS is a barcode-scanner entirely written in JavaScript supporting real-time localization and decoding of various types of barcodes such as __EAN__,
|
||||||
|
__CODE 128__, __CODE 39__, __EAN 8__, __UPC-A__, __UPC-C__, __I2of5__,
|
||||||
|
__2of5__, __CODE 93__, __CODE 32__, __CODABAR__, and __PHARMACODE__. The library is also capable of using
|
||||||
|
`getUserMedia` to get direct access to the user's camera stream. Although the
|
||||||
|
code relies on heavy image-processing even recent smartphones are capable of
|
||||||
|
locating and decoding barcodes in real-time.
|
||||||
|
|
||||||
|
Try some [examples](https://ericblade.github.io/quagga2/examples) and check out
|
||||||
|
the blog post ([How barcode-localization works in QuaggaJS][oberhofer_co_how])
|
||||||
|
if you want to dive deeper into this topic.
|
||||||
|
|
||||||
|
![teaser][teaser_left]![teaser][teaser_right]
|
||||||
|
|
||||||
|
## Yet another barcode library?
|
||||||
|
|
||||||
|
This is not yet another port of the great [zxing][zxing_github] library, but
|
||||||
|
more of an extension to it. This implementation features a barcode locator which
|
||||||
|
is capable of finding a barcode-like pattern in an image resulting in an
|
||||||
|
estimated bounding box including the rotation. Simply speaking, this reader is
|
||||||
|
invariant to scale and rotation, whereas other libraries require the barcode to
|
||||||
|
be aligned with the viewport.
|
||||||
|
|
||||||
|
## <a name="browser-support">Browser Support</a>
|
||||||
|
|
||||||
|
Quagga makes use of many modern Web-APIs which are not implemented by all
|
||||||
|
browsers yet. There are two modes in which Quagga operates:
|
||||||
|
|
||||||
|
1. analyzing static images and
|
||||||
|
2. using a camera to decode the images from a live-stream.
|
||||||
|
|
||||||
|
The latter requires the presence of the MediaDevices API. You can track the compatibility
|
||||||
|
of the used Web-APIs for each mode:
|
||||||
|
|
||||||
|
- [Static Images](http://caniuse.com/#feat=webworkers,canvas,typedarrays,bloburls,blobbuilder)
|
||||||
|
- [Live Stream](http://caniuse.com/#feat=webworkers,canvas,typedarrays,bloburls,blobbuilder,stream)
|
||||||
|
|
||||||
|
### Static Images
|
||||||
|
|
||||||
|
The following APIs need to be implemented in your browser:
|
||||||
|
|
||||||
|
- [canvas](http://caniuse.com/#feat=canvas)
|
||||||
|
- [typedarrays](http://caniuse.com/#feat=typedarrays)
|
||||||
|
- [bloburls](http://caniuse.com/#feat=bloburls)
|
||||||
|
- [blobbuilder](http://caniuse.com/#feat=blobbuilder)
|
||||||
|
|
||||||
|
### Live Stream
|
||||||
|
|
||||||
|
In addition to the APIs mentioned above:
|
||||||
|
|
||||||
|
- [MediaDevices](http://caniuse.com/#feat=stream)
|
||||||
|
|
||||||
|
__Important:__ Accessing `getUserMedia` requires a secure origin in most
|
||||||
|
browsers, meaning that `http://` can only be used on `localhost`. All other
|
||||||
|
hostnames need to be served via `https://`. You can find more information in the
|
||||||
|
[Chrome M47 WebRTC Release Notes](https://groups.google.com/forum/#!topic/discuss-webrtc/sq5CVmY69sc).
|
||||||
|
|
||||||
|
### Feature-detection of getUserMedia
|
||||||
|
|
||||||
|
Every browser seems to differently implement the `mediaDevices.getUserMedia`
|
||||||
|
API. Therefore it's highly recommended to include
|
||||||
|
[webrtc-adapter](https://github.com/webrtc/adapter) in your project.
|
||||||
|
|
||||||
|
Here's how you can test your browser's capabilities:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (navigator.mediaDevices && typeof navigator.mediaDevices.getUserMedia === 'function') {
|
||||||
|
// safely access `navigator.mediaDevices.getUserMedia`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The above condition evaluates to:
|
||||||
|
|
||||||
|
| Browser | result |
|
||||||
|
| ------------- |:-------:|
|
||||||
|
| Edge | `true` |
|
||||||
|
| Chrome | `true` |
|
||||||
|
| Firefox | `true` |
|
||||||
|
| IE 11 | `false` |
|
||||||
|
| Safari iOS | `true` |
|
||||||
|
|
||||||
|
## <a name="installing">Installing</a>
|
||||||
|
|
||||||
|
Quagga2 can be installed using __npm__, or by including it with the __script__ tag.
|
||||||
|
|
||||||
|
### NPM
|
||||||
|
|
||||||
|
```console
|
||||||
|
> npm install --save @ericblade/quagga2
|
||||||
|
```
|
||||||
|
|
||||||
|
And then import it as dependency in your project:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import Quagga from '@ericblade/quagga2'; // ES6
|
||||||
|
const Quagga = require('@ericblade/quagga2').default; // Common JS (important: default)
|
||||||
|
```
|
||||||
|
|
||||||
|
Currently, the full functionality is only available through the browser. When
|
||||||
|
using QuaggaJS within __node__, only file-based decoding is available. See the
|
||||||
|
example for [node_examples](#node-example).
|
||||||
|
|
||||||
|
### Using with script tag
|
||||||
|
|
||||||
|
You can simply include `quagga.js` in your project and you are ready
|
||||||
|
to go. The script exposes the library on the global namespace under `Quagga`.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="quagga.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
You can get the `quagga.js` file in the following ways:
|
||||||
|
|
||||||
|
By [installing the npm module](https://github.com/ericblade/quagga2#npm) and copying the `quagga.js` file from the `dist` folder.
|
||||||
|
|
||||||
|
(OR)
|
||||||
|
|
||||||
|
You can also build the library yourself and copy `quagga.js` file from the `dist` folder(refer to the [building](https://github.com/ericblade/quagga2#building) section for more details)
|
||||||
|
|
||||||
|
(OR)
|
||||||
|
|
||||||
|
You can include the following script tags with CDN links:
|
||||||
|
|
||||||
|
a) `quagga.js`
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
b) `quagga.min.js` (minified version)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.min.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
*Note: You can include a specific version of the library by including the version as shown below.*
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Link for Version 1.2.6 -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2@1.2.6/dist/quagga.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## <a name="gettingstarted">Getting Started</a>
|
||||||
|
|
||||||
|
For starters, have a look at the [examples][github_examples] to get an idea
|
||||||
|
where to go from here.
|
||||||
|
|
||||||
|
## <a name="usingwithreact">Using with React</a>
|
||||||
|
|
||||||
|
There is a separate [example][reactExample] for using quagga2 with ReactJS
|
||||||
|
|
||||||
|
## <a name="usingwithexternalreaders">Using with External Readers</a>
|
||||||
|
|
||||||
|
New in Quagga2 is the ability to specify external reader modules. Please see [quagga2-reader-qr](https://github.com/ericblade/quagga2-reader-qr). This repository includes a sample external reader that can
|
||||||
|
read complete images, and decode QR codes. A test script is included to demonstrate how to use an
|
||||||
|
external reader in your project.
|
||||||
|
|
||||||
|
Quagga2 exports the BarcodeReader prototype, which should also allow you to create new barcode
|
||||||
|
reader implementations using the base BarcodeReader implementation inside Quagga2. The QR reader
|
||||||
|
does not make use of this functionality, as QR is not picked up as a barcode in BarcodeReader.
|
||||||
|
|
||||||
|
### 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']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## <a name="Building">Building</a>
|
||||||
|
|
||||||
|
You can build the library yourself by simply cloning the repo and typing:
|
||||||
|
|
||||||
|
```console
|
||||||
|
> npm install
|
||||||
|
> npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
or using Docker:
|
||||||
|
|
||||||
|
```console
|
||||||
|
> docker build --tag quagga2/build .
|
||||||
|
> docker run -v $(pwd):/quagga2 quagga2/build npm install
|
||||||
|
> docker run -v $(pwd):/quagga2 quagga2/build npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
it's also possible to use docker-compose:
|
||||||
|
|
||||||
|
```console
|
||||||
|
> docker-compose run nodejs npm install
|
||||||
|
> docker-compose run nodejs npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
*Note: when using Docker or docker-compose the build artifacts will end up in `dist/` as usual thanks to the bind-mount.*
|
||||||
|
|
||||||
|
This npm script builds a non optimized version `quagga.js` and a minified
|
||||||
|
version `quagga.min.js` and places both files in the `dist` folder.
|
||||||
|
Additionally, a `quagga.map` source-map is placed alongside these files. This
|
||||||
|
file is only valid for the non-uglified version `quagga.js` because the
|
||||||
|
minified version is altered after compression and does not align with the map
|
||||||
|
file any more.
|
||||||
|
|
||||||
|
## <a name="WorkingWithDev">Working with a development version from another project</a>
|
||||||
|
|
||||||
|
If you are working on a project that includes quagga, but you need to use a development version of
|
||||||
|
quagga, then you can run from the quagga directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install && npm run build && npm link
|
||||||
|
```
|
||||||
|
|
||||||
|
then from the other project directory that needs this quagga, do
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm link @ericblade/quagga2
|
||||||
|
```
|
||||||
|
|
||||||
|
When linking is successful, all future runs of 'npm run build' will update the version that is
|
||||||
|
linked in the project. When combined with an application using webpack-dev-server or some other
|
||||||
|
hot-reload system, you can do very rapid iteration this way.
|
||||||
|
|
||||||
|
### Node
|
||||||
|
|
||||||
|
The code in the `dist` folder is only targeted to the browser and won't work in
|
||||||
|
node due to the dependency on the DOM. For the use in node, the `build` command
|
||||||
|
also creates a `quagga.js` file in the `lib` folder.
|
||||||
|
|
||||||
|
## <a name="api">API</a>
|
||||||
|
|
||||||
|
You can check out the [examples][github_examples] to get an idea of how to
|
||||||
|
use QuaggaJS. Basically the library exposes the following API:
|
||||||
|
|
||||||
|
### <a name="quaggainit">Quagga.init(config, callback)</a>
|
||||||
|
|
||||||
|
This method initializes the library for a given configuration `config` (see
|
||||||
|
below) and invokes the `callback(err)` when Quagga has finished its
|
||||||
|
bootstrapping phase. The initialization process also requests for camera
|
||||||
|
access if real-time detection is configured. In case of an error, the `err`
|
||||||
|
parameter is set and contains information about the cause. A potential cause
|
||||||
|
may be the `inputStream.type` is set to `LiveStream`, but the browser does
|
||||||
|
not support this API, or simply if the user denies the permission to use the
|
||||||
|
camera.
|
||||||
|
|
||||||
|
If you do not specify a target, QuaggaJS would look for an element that matches
|
||||||
|
the CSS selector `#interactive.viewport` (for backwards compatibility).
|
||||||
|
`target` can be a string (CSS selector matching one of your DOM node) or a DOM
|
||||||
|
node.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Quagga.init({
|
||||||
|
inputStream : {
|
||||||
|
name : "Live",
|
||||||
|
type : "LiveStream",
|
||||||
|
target: document.querySelector('#yourElement') // Or '#yourElement' (optional)
|
||||||
|
},
|
||||||
|
decoder : {
|
||||||
|
readers : ["code_128_reader"]
|
||||||
|
}
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log("Initialization finished. Ready to start");
|
||||||
|
Quagga.start();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Quagga.start()
|
||||||
|
|
||||||
|
When the library is initialized, the `start()` method starts the video-stream
|
||||||
|
and begins locating and decoding the images.
|
||||||
|
|
||||||
|
### Quagga.stop()
|
||||||
|
|
||||||
|
If the decoder is currently running, after calling `stop()` the decoder does not
|
||||||
|
process any more images. Additionally, if a camera-stream was requested upon
|
||||||
|
initialization, this operation also disconnects the camera.
|
||||||
|
|
||||||
|
### Quagga.onProcessed(callback)
|
||||||
|
|
||||||
|
This method registers a `callback(data)` function that is called for each frame
|
||||||
|
after the processing is done. The `data` object contains detailed information
|
||||||
|
about the success/failure of the operation. The output varies, depending whether
|
||||||
|
the detection and/or decoding were successful or not.
|
||||||
|
|
||||||
|
### Quagga.onDetected(callback)
|
||||||
|
|
||||||
|
Registers a `callback(data)` function which is triggered whenever a barcode-
|
||||||
|
pattern has been located and decoded successfully. The passed `data` object
|
||||||
|
contains information about the decoding process including the detected code
|
||||||
|
which can be obtained by calling `data.codeResult.code`.
|
||||||
|
|
||||||
|
### Quagga.decodeSingle(config, callback)
|
||||||
|
|
||||||
|
In contrast to the calls described above, this method does not rely on
|
||||||
|
`getUserMedia` and operates on a single image instead. The provided callback
|
||||||
|
is the same as in `onDetected` and contains the result `data` object.
|
||||||
|
|
||||||
|
**Important**: `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 `box`, `boxes`, and
|
||||||
|
`line` coordinates in the result are returned in this scaled coordinate space,
|
||||||
|
not the original image dimensions. To disable scaling and use original dimensions,
|
||||||
|
set `inputStream.size` to `0`.
|
||||||
|
|
||||||
|
### Quagga.offProcessed(handler)
|
||||||
|
|
||||||
|
In case the `onProcessed` event is no longer relevant, `offProcessed` removes
|
||||||
|
the given `handler` from the event-queue. When no handler is passed, all handlers are removed.
|
||||||
|
|
||||||
|
### Quagga.offDetected(handler)
|
||||||
|
|
||||||
|
In case the `onDetected` event is no longer relevant, `offDetected` removes
|
||||||
|
the given `handler` from the event-queue. When no handler is passed, all handlers are removed.
|
||||||
|
|
||||||
|
## <a name="resultobject">The result object</a>
|
||||||
|
|
||||||
|
The callbacks passed into `onProcessed`, `onDetected` and `decodeSingle`
|
||||||
|
receive a `data` object upon execution. The `data` object contains the following
|
||||||
|
information. Depending on the success, some fields may be `undefined` or just
|
||||||
|
empty.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
"codeResult": {
|
||||||
|
"code": "FANAVF1461710", // the decoded code as a string
|
||||||
|
"format": "code_128", // or code_39, codabar, ean_13, ean_8, upc_a, upc_e
|
||||||
|
"start": 355,
|
||||||
|
"end": 26,
|
||||||
|
"codeset": 100,
|
||||||
|
"startInfo": {
|
||||||
|
"error": 1.0000000000000002,
|
||||||
|
"code": 104,
|
||||||
|
"start": 21,
|
||||||
|
"end": 41
|
||||||
|
},
|
||||||
|
"decodedCodes": [{
|
||||||
|
"code": 104,
|
||||||
|
"start": 21,
|
||||||
|
"end": 41
|
||||||
|
},
|
||||||
|
// stripped for brevity
|
||||||
|
{
|
||||||
|
"error": 0.8888888888888893,
|
||||||
|
"code": 106,
|
||||||
|
"start": 328,
|
||||||
|
"end": 350
|
||||||
|
}],
|
||||||
|
"endInfo": {
|
||||||
|
"error": 0.8888888888888893,
|
||||||
|
"code": 106,
|
||||||
|
"start": 328,
|
||||||
|
"end": 350
|
||||||
|
},
|
||||||
|
"direction": -1
|
||||||
|
},
|
||||||
|
"line": [{
|
||||||
|
"x": 25.97278706156836,
|
||||||
|
"y": 360.5616435369468
|
||||||
|
}, {
|
||||||
|
"x": 401.9220519377024,
|
||||||
|
"y": 70.87524989906444
|
||||||
|
}],
|
||||||
|
"angle": -0.6565217179979483,
|
||||||
|
"pattern": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, /* ... */ 1],
|
||||||
|
"box": [
|
||||||
|
[77.4074243622672, 410.9288668804402],
|
||||||
|
[0.050203235235130705, 310.53619724086366],
|
||||||
|
[360.15706727788256, 33.05711026051813],
|
||||||
|
[437.5142884049146, 133.44977990009465]
|
||||||
|
],
|
||||||
|
"boxes": [
|
||||||
|
[
|
||||||
|
[77.4074243622672, 410.9288668804402],
|
||||||
|
[0.050203235235130705, 310.53619724086366],
|
||||||
|
[360.15706727788256, 33.05711026051813],
|
||||||
|
[437.5142884049146, 133.44977990009465]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[248.90769330706507, 415.2041489551161],
|
||||||
|
[198.9532321622869, 352.62160512937635],
|
||||||
|
[339.546160777576, 240.3979259789976],
|
||||||
|
[389.5006219223542, 302.98046980473737]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## <a name="cameraaccess-api">CameraAccess API</a>
|
||||||
|
|
||||||
|
Quagga2 exposes a CameraAccess API that is available for performing some shortcut access to commonly
|
||||||
|
used camera functions. This API is available as `Quagga.CameraAccess` and is documented below.
|
||||||
|
|
||||||
|
**[Full CameraAccess API Documentation](https://ericblade.github.io/quagga2/reference/camera-access.html)** - Detailed reference with examples, error handling, and advanced usage.
|
||||||
|
|
||||||
|
## CameraAccess.request(HTMLVideoElement | null, MediaTrackConstraints?)
|
||||||
|
|
||||||
|
Will attempt to initialize the camera and start playback given the specified video element. Camera
|
||||||
|
is selected by the browser based on the MediaTrackConstraints supplied. If no video element is
|
||||||
|
supplied, the camera will be initialized but invisible. This is mostly useful for probing that the
|
||||||
|
camera is available, or probing to make sure that permissions are granted by the user.
|
||||||
|
This function will return a Promise that resolves when completed, or rejects on error.
|
||||||
|
|
||||||
|
## CameraAccess.release()
|
||||||
|
|
||||||
|
If a video element is known to be running, this will pause the video element, then return a Promise
|
||||||
|
that when resolved will have stopped all tracks in the video element, and released all resources.
|
||||||
|
|
||||||
|
## CameraAccess.enumerateVideoDevices(constraints?)
|
||||||
|
|
||||||
|
This will send out a call to navigator.mediaDevices.enumerateDevices(), filter out any mediadevices
|
||||||
|
that do not have a kind of 'videoinput', and resolve the promise with an array of MediaDeviceInfo.
|
||||||
|
|
||||||
|
Optionally, you can pass MediaTrackConstraints to filter devices. When constraints are provided,
|
||||||
|
only devices that can satisfy the given constraints will be returned. This is useful for filtering
|
||||||
|
out cameras that don't meet your requirements (e.g., eliminating wide-angle cameras).
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Get all video devices
|
||||||
|
const devices = await Quagga.CameraAccess.enumerateVideoDevices();
|
||||||
|
|
||||||
|
// Get only devices that support 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'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## CameraAccess.getActiveStreamLabel()
|
||||||
|
|
||||||
|
Returns the label for the active video track
|
||||||
|
|
||||||
|
## CameraAccess.getActiveTrack()
|
||||||
|
|
||||||
|
Returns the MediaStreamTrack for the active video track
|
||||||
|
|
||||||
|
## CameraAccess.disableTorch()
|
||||||
|
|
||||||
|
Turns off Torch. (Camera Flash) Resolves when complete, throws on error. Does not work on iOS devices of at least version 16.4 and earlier. May or may not work on later versions.
|
||||||
|
|
||||||
|
## CameraAccess.enableTorch()
|
||||||
|
|
||||||
|
Turns on Torch. (Camera Flash) Resolves when complete, throws on error. Does not work on iOS devices of at least version 16.4 and earlier. May or may not work on later versions.
|
||||||
|
|
||||||
|
## <a name="configobject">Configuration</a>
|
||||||
|
|
||||||
|
The configuration that ships with QuaggaJS covers the default use-cases and can
|
||||||
|
be fine-tuned for specific requirements.
|
||||||
|
|
||||||
|
The configuration is managed by the `config` object defining the following
|
||||||
|
high-level properties:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
locate: true,
|
||||||
|
inputStream: {...},
|
||||||
|
frequency: 10,
|
||||||
|
decoder:{...},
|
||||||
|
locator: {...},
|
||||||
|
debug: false,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### locate
|
||||||
|
|
||||||
|
One of the main features of QuaggaJS is its ability to locate a barcode in a
|
||||||
|
given image. The `locate` property controls whether this feature is turned on
|
||||||
|
(default) or off.
|
||||||
|
|
||||||
|
Why would someone turn this feature off? Localizing a barcode is a
|
||||||
|
computationally expensive operation and might not work properly on some
|
||||||
|
devices. Another reason would be the lack of auto-focus producing blurry
|
||||||
|
images which makes the localization feature very unstable.
|
||||||
|
|
||||||
|
However, even if none of the above apply, there is one more case where it might
|
||||||
|
be useful to disable `locate`: If the orientation, and/or the approximate
|
||||||
|
position of the barcode is known, or if you want to guide the user through a
|
||||||
|
rectangular outline. This can increase performance and robustness at the same
|
||||||
|
time.
|
||||||
|
|
||||||
|
### inputStream
|
||||||
|
|
||||||
|
The `inputStream` property defines the sources of images/videos within QuaggaJS.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
name: "Live",
|
||||||
|
type: "LiveStream",
|
||||||
|
constraints: {
|
||||||
|
width: 640,
|
||||||
|
height: 480,
|
||||||
|
facingMode: "environment",
|
||||||
|
deviceId: "7832475934759384534"
|
||||||
|
},
|
||||||
|
area: { // defines rectangle of the detection/localization area
|
||||||
|
top: "0%", // top offset
|
||||||
|
right: "0%", // right offset
|
||||||
|
left: "0%", // left offset
|
||||||
|
bottom: "0%" // bottom offset
|
||||||
|
},
|
||||||
|
singleChannel: false, // true: only the red color-channel is read
|
||||||
|
debug: {
|
||||||
|
showImageDetails: false // logs frame grabber info and canvas size adjustments to console
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
First, the `type` property can be set to three different values:
|
||||||
|
`ImageStream`, `VideoStream`, or `LiveStream` (default) and should be selected
|
||||||
|
depending on the use-case. Most probably, the default value is sufficient.
|
||||||
|
|
||||||
|
Second, the `constraint` key defines the physical dimensions of the input image
|
||||||
|
and additional properties, such as `facingMode` which sets the source of the
|
||||||
|
user's camera in case of multiple attached devices. Additionally, if required,
|
||||||
|
the `deviceId` can be set if the selection of the camera is given to the user.
|
||||||
|
This can be easily achieved via
|
||||||
|
[MediaDevices.enumerateDevices()][enumerateDevices]
|
||||||
|
|
||||||
|
Thirdly, the `area` prop restricts the decoding area of the image. The values
|
||||||
|
are given in percentage, similar to the CSS style property when using
|
||||||
|
`position: absolute`. This `area` is also useful in cases the `locate` property
|
||||||
|
is set to `false`, defining the rectangle for the user.
|
||||||
|
|
||||||
|
The last key `singleChannel` is only relevant in cases someone wants to debug
|
||||||
|
erroneous behavior of the decoder. If set to `true` the input image's red
|
||||||
|
color-channel is read instead of calculating the gray-scale values of the
|
||||||
|
source's RGB. This is useful in combination with the `ResultCollector` where
|
||||||
|
the gray-scale representations of the wrongly identified images are saved.
|
||||||
|
|
||||||
|
### frequency
|
||||||
|
|
||||||
|
This top-level property controls the scan-frequency of the video-stream. It's
|
||||||
|
optional and defines the maximum number of scans per second. This renders
|
||||||
|
useful for cases where the scan-session is long-running and resources such as
|
||||||
|
CPU power are of concern.
|
||||||
|
|
||||||
|
Note: `frequency` specifies a maximum, not an absolute rate. If the system
|
||||||
|
cannot achieve the requested frequency due to CPU limitations, decoding
|
||||||
|
complexity, or other factors, scans will occur as fast as the system allows.
|
||||||
|
For example, if you set `frequency: 10` but your system can only process 8
|
||||||
|
scans per second, you will get approximately 8 scans per second.
|
||||||
|
|
||||||
|
### decoder
|
||||||
|
|
||||||
|
QuaggaJS usually runs in a two-stage manner (`locate` is set to `true`) where,
|
||||||
|
after the barcode is located, the decoding process starts. Decoding is the
|
||||||
|
process of converting the bars into their true meaning. Most of the configuration
|
||||||
|
options within the `decoder` are for debugging/visualization purposes only.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
readers: [
|
||||||
|
'code_128_reader'
|
||||||
|
],
|
||||||
|
debug: {
|
||||||
|
drawBoundingBox: false,
|
||||||
|
showFrequency: false,
|
||||||
|
drawScanline: false,
|
||||||
|
showPattern: false,
|
||||||
|
printReaderInfo: false // logs reader registration and initialization to console
|
||||||
|
}
|
||||||
|
multiple: false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The most important property is `readers` which takes an array of types of
|
||||||
|
barcodes which should be decoded during the session. Possible values are:
|
||||||
|
|
||||||
|
- code_128_reader (default)
|
||||||
|
- ean_reader
|
||||||
|
- ean_8_reader
|
||||||
|
- code_39_reader
|
||||||
|
- code_39_vin_reader
|
||||||
|
- codabar_reader
|
||||||
|
- upc_reader
|
||||||
|
- upc_e_reader
|
||||||
|
- i2of5_reader
|
||||||
|
- 2of5_reader
|
||||||
|
- code_93_reader
|
||||||
|
- code_32_reader
|
||||||
|
- pharmacode_reader
|
||||||
|
|
||||||
|
Why are not all types activated by default? Simply because one should
|
||||||
|
explicitly define the set of barcodes for their use-case. More decoders means
|
||||||
|
more possible clashes, or false-positives. One should take care of the order
|
||||||
|
the readers are given, since some might return a value even though it is not
|
||||||
|
the correct type (EAN-13 vs. UPC-A).
|
||||||
|
|
||||||
|
#### 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. This allows you to
|
||||||
|
prioritize certain formats over others when multiple formats might match the
|
||||||
|
same barcode pattern.
|
||||||
|
|
||||||
|
For example, if you know most of your barcodes are EAN-13, place `ean_reader`
|
||||||
|
first to ensure it attempts to decode before other readers:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
decoder: {
|
||||||
|
readers: ['ean_reader', 'upc_reader', 'upc_e_reader'] // EAN-13 checked first
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is particularly important when dealing with barcode formats that share
|
||||||
|
similar patterns (like EAN-13 and UPC-A/UPC-E), where incorrect matches might
|
||||||
|
occur if the wrong reader attempts to decode first.
|
||||||
|
|
||||||
|
The `multiple` property tells the decoder if it should continue decoding after
|
||||||
|
finding a valid barcode. If multiple is set to `true`, the results will be
|
||||||
|
returned as an array of result objects. Each object in the array will have a
|
||||||
|
`box`, and may have a `codeResult` depending on the success of decoding the
|
||||||
|
individual box.
|
||||||
|
|
||||||
|
The remaining properties `drawBoundingBox`, `showFrequency`, `drawScanline` and
|
||||||
|
`showPattern` are mostly of interest during debugging and visualization.
|
||||||
|
|
||||||
|
#### <a name="ean_extended">Enabling extended EAN</a>
|
||||||
|
|
||||||
|
The default setting for `ean_reader` is not capable of reading extensions such
|
||||||
|
as [EAN-2](https://en.wikipedia.org/wiki/EAN_2) or
|
||||||
|
[EAN-5](https://en.wikipedia.org/wiki/EAN_5). In order to activate those
|
||||||
|
supplements you have to provide them in the configuration as followed:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
decoder: {
|
||||||
|
readers: [{
|
||||||
|
format: "ean_reader",
|
||||||
|
config: {
|
||||||
|
supplements: [
|
||||||
|
'ean_5_reader', 'ean_2_reader'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Beware that the order of the `supplements` matters in such that the reader stops
|
||||||
|
decoding when the first supplement was found. So if you are interested in EAN-2
|
||||||
|
and EAN-5 extensions, use the order depicted above.
|
||||||
|
|
||||||
|
It's important to mention that, if supplements are supplied, regular EAN-13
|
||||||
|
codes cannot be read any more with the same reader. If you want to read EAN-13
|
||||||
|
with and without extensions you have to add another `ean_reader` reader to the
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
### locator
|
||||||
|
|
||||||
|
The `locator` config is only relevant if the `locate` flag is set to `true`.
|
||||||
|
It controls the behavior of the localization-process and needs to be adjusted
|
||||||
|
for each specific use-case. The default settings are simply a combination of
|
||||||
|
values which worked best during development.
|
||||||
|
|
||||||
|
Only two properties are relevant for the use in Quagga (`halfSample` and
|
||||||
|
`patchSize`) whereas the rest is only needed for development and debugging.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
halfSample: true,
|
||||||
|
patchSize: "medium", // x-small, small, medium, large, x-large
|
||||||
|
debug: {
|
||||||
|
showCanvas: false,
|
||||||
|
showPatches: false,
|
||||||
|
showFoundPatches: false,
|
||||||
|
showSkeleton: false,
|
||||||
|
showLabels: false,
|
||||||
|
showPatchLabels: false,
|
||||||
|
showRemainingPatchLabels: false,
|
||||||
|
showPatchSize: false, // logs calculated patch size to console
|
||||||
|
showImageDetails: false, // logs image wrapper size and canvas details to console
|
||||||
|
boxFromPatches: {
|
||||||
|
showTransformed: false,
|
||||||
|
showTransformedBox: false,
|
||||||
|
showBB: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `halfSample` flag tells the locator-process whether it should operate on an
|
||||||
|
image scaled down (half width/height, quarter pixel-count ) or not. Turning
|
||||||
|
`halfSample` on reduces the processing-time significantly and also helps
|
||||||
|
finding a barcode pattern due to implicit smoothing.
|
||||||
|
It should be turned off in cases where the barcode is really small and the full
|
||||||
|
resolution is needed to find the position. It's recommended to keep it turned
|
||||||
|
on and use a higher resolution video-image if needed.
|
||||||
|
|
||||||
|
The second property `patchSize` defines the density of the search-grid. The
|
||||||
|
property accepts strings of the value `x-small`, `small`, `medium`, `large` and
|
||||||
|
`x-large`. The `patchSize` is proportional to the size of the scanned barcodes.
|
||||||
|
If you have really large barcodes which can be read close-up, then the use of
|
||||||
|
`large` or `x-large` is recommended. In cases where the barcode is further away
|
||||||
|
from the camera lens (lack of auto-focus, or small barcodes) then it's advised
|
||||||
|
to set the size to `small` or even `x-small`. For the latter it's also
|
||||||
|
recommended to crank up the resolution in order to find a barcode.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
The following example takes an image `src` as input and prints the result on the
|
||||||
|
console. The decoder is configured to detect *Code128* barcodes and enables the
|
||||||
|
locating-mechanism for more robust results.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Quagga.decodeSingle({
|
||||||
|
decoder: {
|
||||||
|
readers: ["code_128_reader"] // List of active readers
|
||||||
|
},
|
||||||
|
locate: true, // try to locate the barcode in the image
|
||||||
|
src: '/test/fixtures/code_128/image-001.jpg' // or 'data:image/jpg;base64,' + data
|
||||||
|
// Note: inputStream.size defaults to 800; images are scaled to 800px (up or down).
|
||||||
|
}, function(result){
|
||||||
|
if(result.codeResult) {
|
||||||
|
console.log("result", result.codeResult.code);
|
||||||
|
} else {
|
||||||
|
console.log("not detected");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### <a name="node-example">Using node</a>
|
||||||
|
|
||||||
|
The following example illustrates the use of QuaggaJS within a node
|
||||||
|
environment. It's almost identical to the browser version with the difference
|
||||||
|
that node does not support web-workers out of the box. Therefore the config
|
||||||
|
property `numOfWorkers` must be explicitly set to `0`.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var Quagga = require('quagga').default;
|
||||||
|
|
||||||
|
Quagga.decodeSingle({
|
||||||
|
src: "image-abc-123.jpg",
|
||||||
|
numOfWorkers: 0, // Needs to be 0 when used within node
|
||||||
|
inputStream: {
|
||||||
|
size: 800 // This is the default; shown explicitly for clarity
|
||||||
|
},
|
||||||
|
decoder: {
|
||||||
|
readers: ["code_128_reader"] // List of active readers
|
||||||
|
},
|
||||||
|
}, function(result) {
|
||||||
|
if(result.codeResult) {
|
||||||
|
console.log("result", result.codeResult.code);
|
||||||
|
} else {
|
||||||
|
console.log("not detected");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## <a name="tipsandtricks">Tips & Tricks</a>
|
||||||
|
|
||||||
|
A growing collection of tips & tricks to improve the various aspects of Quagga.
|
||||||
|
|
||||||
|
### Working with Cordova / PhoneGap?
|
||||||
|
|
||||||
|
If you're having issues getting a mobile device to run Quagga using Cordova, you might try the code
|
||||||
|
here: [Original Repo Issue #94 Comment][issue-94-comment]
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
let permissions = cordova.plugins.permissions; permissions.checkPermission(permissions.CAMERA,
|
||||||
|
(res) => { if (!res.hasPermission) { permissions.requestPermission(permissions.CAMERA, open());
|
||||||
|
```
|
||||||
|
|
||||||
|
Thanks, @chrisrodriguezmbww !
|
||||||
|
|
||||||
|
### Barcodes too small?
|
||||||
|
|
||||||
|
Barcodes too far away from the camera, or a lens too close to the object
|
||||||
|
result in poor recognition rates and Quagga might respond with a lot of
|
||||||
|
false-positives.
|
||||||
|
|
||||||
|
Starting in Chrome 59 you can now make use of `capabilities` and directly
|
||||||
|
control the zoom of the camera. Head over to the
|
||||||
|
[web-cam demo](https://ericblade.github.io/quagga2/examples/live_w_locator.html)
|
||||||
|
and check out the __Zoom__ feature.
|
||||||
|
|
||||||
|
You can read more about those `capabilities` in
|
||||||
|
[Let's light a torch and explore MediaStreamTrack's capabilities](https://www.oberhofer.co/mediastreamtrack-and-its-capabilities)
|
||||||
|
|
||||||
|
### Video too dark?
|
||||||
|
|
||||||
|
Dark environments usually result in noisy images and therefore mess with the
|
||||||
|
recognition logic.
|
||||||
|
|
||||||
|
Since Chrome 59 you can turn on/off the __Torch__ of your device and vastly
|
||||||
|
improve the quality of the images. Head over to the
|
||||||
|
[web-cam demo](https://ericblade.github.io/quagga2/examples/live_w_locator.html)
|
||||||
|
and check out the __Torch__ feature.
|
||||||
|
|
||||||
|
To find out more about this feature [read on](https://www.oberhofer.co/mediastreamtrack-and-its-capabilities).
|
||||||
|
|
||||||
|
### Handling false positives
|
||||||
|
|
||||||
|
Most readers provide an error object that describes the confidence of the reader in it's accuracy. There are strategies you can implement in your application to improve what your application accepts as acceptable input from the barcode scanner, [in this thread](https://github.com/serratus/quaggaJS/issues/237).
|
||||||
|
|
||||||
|
If you choose to explore check-digit validation, you might find [barcode-validator](https://github.com/ericblade/barcode-validator) a useful library.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Tests are performed with [Cypress][cypressUrl] for browser testing, and [Mocha][mochaUrl], [Chai][chaiUrl], and [SinonJS][sinonUrl] for Node.JS testing. (note that Cypress also uses Mocha, Chai, and Sinon, so tests that are not browser specific can be run virtually identically in node without duplication of code)
|
||||||
|
|
||||||
|
Coverage reports are generated in the coverage/ folder.
|
||||||
|
|
||||||
|
```console
|
||||||
|
> npm install
|
||||||
|
> npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
Using Docker:
|
||||||
|
|
||||||
|
```console
|
||||||
|
> docker build --tag quagga2/build .
|
||||||
|
> docker run -v $(pwd):/quagga2 npm install
|
||||||
|
> docker run -v $(pwd):/quagga2 npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
or using docker-compose:
|
||||||
|
|
||||||
|
```console
|
||||||
|
> docker-compose run nodejs npm install
|
||||||
|
> docker-compose run nodejs npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
We prefer that Unit tests be located near the unit being tested -- the src/quagga/transform module, for example, has it's test suite located at src/quagga/test/transform.spec.ts. Likewise, src/locator/barcode_locator test is located at src/locator/test/barcode_locator.spec.ts .
|
||||||
|
|
||||||
|
If you have browser or node specific tests, that must be written differently per platform, or do not apply to one platform, then you may add them to src/{filelocation}/test/browser or .../test/node. See also src/analytics/test/browser/result_collector.spec.ts, which contains browser specific code.
|
||||||
|
|
||||||
|
If you add a new test file, you should also make sure to import it in either cypress/integration/browser.spec.ts, for browser-specific tests, or cypress/integration/universal.spec.ts, for tests that can be run both in node and in browser. Node.JS testing is performed using the power of file globbing, and will pick up your tests, so long as they conform to the existing test file directory and name patterns.
|
||||||
|
|
||||||
|
## Image Debugging
|
||||||
|
|
||||||
|
In case you want to take a deeper dive into the inner workings of Quagga, get to
|
||||||
|
know the *debugging* capabilities of the current implementation. The various
|
||||||
|
flags exposed through the `config` object give you the ability to visualize
|
||||||
|
almost every step in the processing. Because of the introduction of the
|
||||||
|
web-workers, and their restriction not to have access to the DOM, the
|
||||||
|
configuration must be explicitly set to `config.numOfWorkers = 0` in order to
|
||||||
|
work.
|
||||||
|
|
||||||
|
## <a name="resultcollector">ResultCollector</a>
|
||||||
|
|
||||||
|
Quagga is not perfect by any means and may produce false positives from time
|
||||||
|
to time. In order to find out which images produced those false positives,
|
||||||
|
the built-in ``ResultCollector`` will support you and me helping squashing
|
||||||
|
bugs in the implementation.
|
||||||
|
|
||||||
|
### Creating a ``ResultCollector``
|
||||||
|
|
||||||
|
You can easily create a new ``ResultCollector`` by calling its ``create``
|
||||||
|
method with a configuration.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var resultCollector = Quagga.ResultCollector.create({
|
||||||
|
capture: true, // keep track of the image producing this result
|
||||||
|
capacity: 20, // maximum number of results to store
|
||||||
|
blacklist: [ // list containing codes which should not be recorded
|
||||||
|
{code: "3574660239843", format: "ean_13"}],
|
||||||
|
filter: function(codeResult) {
|
||||||
|
// only store results which match this constraint
|
||||||
|
// returns true/false
|
||||||
|
// e.g.: return codeResult.format === "ean_13";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using a ``ResultCollector``
|
||||||
|
|
||||||
|
After creating a ``ResultCollector`` you have to attach it to Quagga by
|
||||||
|
calling ``Quagga.registerResultCollector(resultCollector)``.
|
||||||
|
|
||||||
|
### Reading results
|
||||||
|
|
||||||
|
After a test/recording session, you can now print the collected results which
|
||||||
|
do not fit into a certain schema. Calling ``getResults`` on the
|
||||||
|
``resultCollector`` returns an ``Array`` containing objects with:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
codeResult: {}, // same as in onDetected event
|
||||||
|
frame: "data:image/png;base64,iVBOR..." // dataURL of the gray-scaled image
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The ``frame`` property is an internal representation of the image and
|
||||||
|
therefore only available in gray-scale. The dataURL representation allows
|
||||||
|
easy saving/rendering of the image.
|
||||||
|
|
||||||
|
### Comparing results
|
||||||
|
|
||||||
|
Now, having the frames available on disk, you can load each single image by
|
||||||
|
calling ``decodeSingle`` with the same configuration as used during recording
|
||||||
|
. In order to reproduce the exact same result, you have to make sure to turn
|
||||||
|
on the ``singleChannel`` flag in the configuration when using ``decodeSingle``.
|
||||||
|
|
||||||
|
[zxing_github]: https://github.com/zxing/zxing
|
||||||
|
[teaser_left]: https://raw.githubusercontent.com/serratus/quaggaJS/master/doc/img/mobile-located.png
|
||||||
|
[teaser_right]: https://raw.githubusercontent.com/serratus/quaggaJS/master/doc/img/mobile-detected.png
|
||||||
|
[caniuse_getusermedia]: http://caniuse.com/#feat=stream
|
||||||
|
[cypressUrl]: https://www.cypress.io/
|
||||||
|
[sinonUrl]: http://sinonjs.org/
|
||||||
|
[chaiUrl]: http://chaijs.com/
|
||||||
|
[mochaUrl]: https://github.com/mochajs/mocha
|
||||||
|
[code39_wiki]: http://en.wikipedia.org/wiki/Code_39
|
||||||
|
[codabar_wiki]: http://en.wikipedia.org/wiki/Codabar
|
||||||
|
[upc_wiki]: http://en.wikipedia.org/wiki/Universal_Product_Code
|
||||||
|
[ean_8_wiki]: http://en.wikipedia.org/wiki/EAN-8
|
||||||
|
[oberhofer_co_how]: http://www.oberhofer.co/how-barcode-localization-works-in-quaggajs/
|
||||||
|
[github_examples]: https://ericblade.github.io/quagga2/examples
|
||||||
|
[i2of5_wiki]: https://en.wikipedia.org/wiki/Interleaved_2_of_5
|
||||||
|
[enumerateDevices]: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices
|
||||||
|
[reactExample]: https://github.com/ericblade/quagga2-react-example
|
||||||
|
[issue-94-comment]: https://github.com/serratus/quaggajs/issues/94#issuecomment-571478711
|
||||||
|
#### Sequence Mode
|
||||||
|
|
||||||
|
Quagga2 supports loading a sequence of images for batch processing using the `inputStream.sequence` option. When enabled, Quagga will attempt to load images named in the pattern `image-001.jpg`, `image-002.jpg`, etc., from the specified directory.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```javascript
|
||||||
|
Quagga.init({
|
||||||
|
inputStream: {
|
||||||
|
type: 'ImageStream',
|
||||||
|
sequence: true,
|
||||||
|
size: 3, // Number of images to load
|
||||||
|
offset: 1, // Starting index (optional)
|
||||||
|
src: '/path/to/images/', // Base path for images
|
||||||
|
},
|
||||||
|
decoder: { readers: ['code_128_reader'] }
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
This will load `/path/to/images/image-001.jpg`, `/path/to/images/image-002.jpg`, `/path/to/images/image-003.jpg` for processing.
|
||||||
|
|
||||||
|
Sequence mode is useful for batch testing or processing multiple images with predictable filenames.
|
||||||
5
quagga2/quagga2-1.12.1/configs/env/development.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
production: false,
|
||||||
|
development: true,
|
||||||
|
node: false,
|
||||||
|
};
|
||||||
5
quagga2/quagga2-1.12.1/configs/env/node.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
production: true,
|
||||||
|
development: false,
|
||||||
|
node: true,
|
||||||
|
};
|
||||||
5
quagga2/quagga2-1.12.1/configs/env/production.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
production: true,
|
||||||
|
development: false,
|
||||||
|
node: false,
|
||||||
|
};
|
||||||
78
quagga2/quagga2-1.12.1/configs/webpack.config.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
var webpack = require('webpack'),
|
||||||
|
path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: [
|
||||||
|
'./src/quagga.js',
|
||||||
|
],
|
||||||
|
devtool: 'inline-source-map',
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
include: [
|
||||||
|
path.resolve('./node_modules/sinon'),
|
||||||
|
path.resolve('./node_modules/@sinonjs')
|
||||||
|
],
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
presets: [
|
||||||
|
['@babel/preset-env', {
|
||||||
|
targets: { browsers: ['last 2 versions'] }
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
'@babel/plugin-proposal-optional-chaining',
|
||||||
|
'@babel/plugin-proposal-nullish-coalescing-operator'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(t|j)sx?$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: { loader: 'babel-loader' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enforce: 'pre',
|
||||||
|
test: /\.(t|j)sx?$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
loader: 'source-map-loader',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
||||||
|
modules: [
|
||||||
|
path.resolve('./src'),
|
||||||
|
'node_modules',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
fs: 'empty',
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: __dirname + '/../dist',
|
||||||
|
publicPath: '/',
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
libraryExport: 'default',
|
||||||
|
library: 'Quagga',
|
||||||
|
filename: 'quagga.js',
|
||||||
|
},
|
||||||
|
devServer: {
|
||||||
|
contentBase: './',
|
||||||
|
hot: true,
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
ENV: require(path.join(__dirname, './env/', process.env.BUILD_ENV)),
|
||||||
|
}),
|
||||||
|
new webpack.NormalModuleReplacementPlugin(/..\/input\/frame_grabber/, '../input/frame_grabber_browser.js'),
|
||||||
|
new webpack.NormalModuleReplacementPlugin(/input_stream[/\\]input_stream$/, (resource) => {
|
||||||
|
resource.request = resource.request.replace(/input_stream$/, 'input_stream_browser');
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
optimization: {
|
||||||
|
minimize: false,
|
||||||
|
},
|
||||||
|
mode: 'production',
|
||||||
|
};
|
||||||
15
quagga2/quagga2-1.12.1/configs/webpack.config.min.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
var webpack = require('webpack');
|
||||||
|
module.exports = require('./webpack.config.js');
|
||||||
|
|
||||||
|
module.exports.plugins = module.exports.plugins.concat([
|
||||||
|
new webpack.LoaderOptionsPlugin({
|
||||||
|
minimize: true,
|
||||||
|
debug: false,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
module.exports.optimization.minimize = true;
|
||||||
|
module.exports.output.filename = 'quagga.min.js';
|
||||||
|
|
||||||
|
module.exports.output.sourceMapFilename = '';
|
||||||
|
module.exports.devtool = false;
|
||||||
36
quagga2/quagga2-1.12.1/configs/webpack.node.config.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
var webpack = require('webpack'),
|
||||||
|
path = require('path');
|
||||||
|
|
||||||
|
const baseConfig = require('./webpack.config.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...baseConfig,
|
||||||
|
externals: [
|
||||||
|
'fs',
|
||||||
|
'http',
|
||||||
|
'https',
|
||||||
|
'url',
|
||||||
|
'sharp',
|
||||||
|
'ndarray-pixels',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
module.exports.target = 'node';
|
||||||
|
module.exports.resolve = {
|
||||||
|
...module.exports.resolve,
|
||||||
|
// Prefer Node builds of packages (e.g., ndarray-pixels) instead of browser entries
|
||||||
|
mainFields: ['main', 'module'],
|
||||||
|
alias: {
|
||||||
|
// Force ndarray-pixels to resolve to its Node build; webpack 4 lacks conditionNames support
|
||||||
|
'ndarray-pixels$': 'ndarray-pixels/dist/ndarray-pixels-node.cjs',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
module.exports.output.libraryTarget = 'commonjs';
|
||||||
|
module.exports.output.library = undefined;
|
||||||
|
module.exports.plugins = [
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
ENV: require(path.join(__dirname, './env/', process.env.BUILD_ENV)),
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
module.exports.devtool = undefined;
|
||||||
|
module.exports.output.path = __dirname + '/../lib';
|
||||||
|
module.exports.output.filename = 'quagga.js';
|
||||||
14
quagga2/quagga2-1.12.1/cypress.config.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { defineConfig } from 'cypress'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
video: false,
|
||||||
|
screenshotOnRunFailure: false,
|
||||||
|
trashAssetsBeforeRuns: true,
|
||||||
|
e2e: {
|
||||||
|
// We've imported your old cypress plugins here.
|
||||||
|
// You may want to clean this up later by importing these.
|
||||||
|
setupNodeEvents(on, config) {
|
||||||
|
return require('./cypress/plugins/index.js')(on, config)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
62
quagga2/quagga2-1.12.1/cypress/e2e/browser-bundle.cy.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// Test that the browser bundle (dist/quagga.min.js) works correctly with decodeSingle
|
||||||
|
// This validates that the built bundle can decode images in a browser context
|
||||||
|
|
||||||
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
|
describe('Browser Bundle - decodeSingle', () => {
|
||||||
|
before(() => {
|
||||||
|
// Create a minimal test HTML file
|
||||||
|
const html = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Bundle Test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="/dist/quagga.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
cy.writeFile('cypress/fixtures/bundle-test.html', html);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
// Clean up the temporary file even if test passes
|
||||||
|
cy.task('cleanupBundleFixture');
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/cypress/fixtures/bundle-test.html');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should decode a Code 128 barcode from dist/quagga.min.js', () => {
|
||||||
|
cy.window().should('have.property', 'Quagga');
|
||||||
|
|
||||||
|
cy.window().then((win) => {
|
||||||
|
return new Cypress.Promise((resolve, reject) => {
|
||||||
|
// Use a fixture image path relative to the server root
|
||||||
|
const imagePath = '/test/fixtures/code_128/image-001.jpg';
|
||||||
|
|
||||||
|
win.Quagga.decodeSingle({
|
||||||
|
src: imagePath,
|
||||||
|
numOfWorkers: 0,
|
||||||
|
inputStream: {
|
||||||
|
size: 800
|
||||||
|
},
|
||||||
|
decoder: {
|
||||||
|
readers: ['code_128_reader']
|
||||||
|
},
|
||||||
|
}, (result: any) => {
|
||||||
|
if (result && result.codeResult) {
|
||||||
|
cy.log('Decoded:', result.codeResult.code);
|
||||||
|
expect(result.codeResult.code).to.be.a('string');
|
||||||
|
expect(result.codeResult.code.length).to.be.greaterThan(0);
|
||||||
|
resolve(result);
|
||||||
|
} else {
|
||||||
|
reject(new Error('Failed to decode barcode'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
11
quagga2/quagga2-1.12.1/cypress/e2e/browser.cy.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
window.ENV = { development: true, production: false, node: false };
|
||||||
|
|
||||||
|
import '../../src/analytics/test/browser/result_collector.spec.ts';
|
||||||
|
import '../../src/input/test/browser/camera_access.spec.ts';
|
||||||
|
import '../../src/input/test/browser/exif_helper.spec';
|
||||||
|
import '../../src/input/test/browser/image_loader.spec.ts';
|
||||||
|
import '../../src/input/test/browser/data_uri.spec.ts';
|
||||||
|
import '../../src/common/test/browser/mediaDevices.spec.ts';
|
||||||
|
import '../../src/input/test/browser/frame_grabber_halfsample.spec.ts';
|
||||||
|
import '../../src/quagga/test/browser/pause_start.spec.ts';
|
||||||
|
import '../../src/quagga/test/browser/initCanvas.spec.ts';
|
||||||
156
quagga2/quagga2-1.12.1/cypress/e2e/examples.cy.ts
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
// E2E tests for example pages
|
||||||
|
describe('Example Pages E2E', () => {
|
||||||
|
describe('live_w_locator.html', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('http://localhost:8080/live_w_locator.html');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cy.window().then((win: any) => {
|
||||||
|
if (win.Quagga && win.Quagga.stop) {
|
||||||
|
const stopPromise = win.Quagga.stop();
|
||||||
|
if (stopPromise && typeof stopPromise.then === 'function') {
|
||||||
|
return cy.wrap(stopPromise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.window().then((win: any) => {
|
||||||
|
if (win.Quagga && win.Quagga.stop) {
|
||||||
|
const stopPromise = win.Quagga.stop();
|
||||||
|
if (stopPromise && typeof stopPromise.then === 'function') {
|
||||||
|
return cy.wrap(stopPromise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load the page successfully', () => {
|
||||||
|
cy.contains('h1', 'QuaggaJS').should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a camera dropdown', () => {
|
||||||
|
cy.get('select#deviceSelection').should('exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a locate checkbox', () => {
|
||||||
|
cy.get('input[name="locate"]').should('exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have canvas elements', () => {
|
||||||
|
// Canvas is created by Quagga inside #interactive div
|
||||||
|
cy.get('#interactive').should('exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have Quagga available on window', () => {
|
||||||
|
cy.window().its('Quagga').should('exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should toggle locate checkbox', () => {
|
||||||
|
cy.get('input[name="locate"]').uncheck().should('not.be.checked');
|
||||||
|
cy.get('input[name="locate"]').check().should('be.checked');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should populate camera dropdown with devices', () => {
|
||||||
|
// Wait for devices to be enumerated and dropdown populated
|
||||||
|
cy.get('select#deviceSelection option', { timeout: 10000 })
|
||||||
|
.should('have.length.at.least', 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('provides scanning area box when locate=false', () => {
|
||||||
|
// Wait for Quagga to be initialized and processing frames before toggling locate
|
||||||
|
cy.window().then((win: any) => {
|
||||||
|
expect(win.Quagga).to.exist;
|
||||||
|
return new Cypress.Promise((resolve) => {
|
||||||
|
const timeout = setTimeout(() => resolve(null), 1500);
|
||||||
|
win.Quagga.onProcessed(() => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now toggle locate off and wait for reinit
|
||||||
|
cy.get('input[name="locate"]').uncheck().should('not.be.checked').trigger('change');
|
||||||
|
cy.wait(400); // Allow time for debounced reinit (250ms) + init completion
|
||||||
|
|
||||||
|
cy.window().then((win: any) => {
|
||||||
|
expect(win.Quagga).to.exist;
|
||||||
|
expect(win.Quagga.onProcessed).to.be.a('function');
|
||||||
|
|
||||||
|
const p = new Cypress.Promise((resolve) => {
|
||||||
|
const timeout = setTimeout(() => resolve(null), 2000);
|
||||||
|
|
||||||
|
win.Quagga.onProcessed(function (result: any) {
|
||||||
|
if (result && (result.box || (result.boxes && result.boxes.length > 0))) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return cy.wrap(p, { timeout: 3000 });
|
||||||
|
}).then((result: any) => {
|
||||||
|
expect(result, 'Should receive result with box or boxes').to.exist;
|
||||||
|
// When locate=false, should have either result.box or result.boxes
|
||||||
|
const hasBox = result.box && Array.isArray(result.box) && result.box.length === 4;
|
||||||
|
const hasBoxes = result.boxes && Array.isArray(result.boxes) && result.boxes.length > 0;
|
||||||
|
expect(hasBox || hasBoxes, 'Should have either result.box or result.boxes with scanning area').to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('continues processing frames after resolution change', () => {
|
||||||
|
// Select a different resolution option dynamically (choose last option if available)
|
||||||
|
cy.contains('label', 'Resolution (width)')
|
||||||
|
.find('select[name="input-stream_constraints"]').then($sel => {
|
||||||
|
const current = ($sel[0] as HTMLSelectElement).value;
|
||||||
|
const options = Array.from(($sel[0] as HTMLSelectElement).options).map(o => o.value);
|
||||||
|
const alt = options.reverse().find(v => v !== current) || current;
|
||||||
|
cy.wrap($sel).select(alt);
|
||||||
|
});
|
||||||
|
// Verify frames continue after resolution change
|
||||||
|
cy.window().then((win: any) => {
|
||||||
|
expect(win.Quagga).to.exist;
|
||||||
|
expect(win.Quagga.onProcessed).to.be.a('function');
|
||||||
|
let count = 0;
|
||||||
|
const p = new Cypress.Promise((resolve) => {
|
||||||
|
const timeout = setTimeout(() => resolve(null), 3000);
|
||||||
|
win.Quagga.onProcessed(() => {
|
||||||
|
count++;
|
||||||
|
if (count >= 3) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return cy.wrap(p, { timeout: 3500 }).then(() => {
|
||||||
|
expect(count).to.be.greaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('continues processing frames after patch size change', () => {
|
||||||
|
cy.contains('label', 'Patch-Size')
|
||||||
|
.find('select[name="locator_patch-size"]').select('large');
|
||||||
|
cy.window().then((win: any) => {
|
||||||
|
expect(win.Quagga).to.exist;
|
||||||
|
expect(win.Quagga.onProcessed).to.be.a('function');
|
||||||
|
let count = 0;
|
||||||
|
const p = new Cypress.Promise((resolve) => {
|
||||||
|
win.Quagga.onProcessed(() => { count++; if (count >= 3) resolve(null); });
|
||||||
|
});
|
||||||
|
return cy.wrap(p, { timeout: 3000 }).then(() => {
|
||||||
|
expect(count).to.be.greaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides zoom and torch controls when unsupported', () => {
|
||||||
|
// Labels wrap the inputs and are set to display:none when capability missing
|
||||||
|
cy.get('label:has(select[name="settings_zoom"])').should('have.css', 'display', 'none');
|
||||||
|
cy.get('label:has(input[name="settings_torch"])').should('have.css', 'display', 'none');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
22
quagga2/quagga2-1.12.1/cypress/e2e/integration.cy.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
window.ENV = { development: true, production: false, node: false };
|
||||||
|
|
||||||
|
// Integration tests that aren't specific decoders
|
||||||
|
import '../../test/integration/external-reader.spec.ts';
|
||||||
|
import '../../test/integration/integration.spec.ts';
|
||||||
|
import '../../test/integration/reader-order.spec.ts';
|
||||||
|
|
||||||
|
// Decoders
|
||||||
|
import '../../test/integration/decoders/2of5.spec.ts';
|
||||||
|
import '../../test/integration/decoders/codabar.spec.ts';
|
||||||
|
import '../../test/integration/decoders/code_128.spec.ts';
|
||||||
|
import '../../test/integration/decoders/code_32.spec.ts';
|
||||||
|
import '../../test/integration/decoders/code_39_vin.spec.ts';
|
||||||
|
import '../../test/integration/decoders/code_39.spec.ts';
|
||||||
|
import '../../test/integration/decoders/code_93.spec.ts';
|
||||||
|
import '../../test/integration/decoders/ean_8.spec.ts';
|
||||||
|
import '../../test/integration/decoders/ean_extended.spec.ts';
|
||||||
|
import '../../test/integration/decoders/ean_supplement_format.spec.ts';
|
||||||
|
import '../../test/integration/decoders/ean.spec.ts';
|
||||||
|
import '../../test/integration/decoders/i2of5.spec.ts';
|
||||||
|
import '../../test/integration/decoders/upc_e.spec.ts';
|
||||||
|
import '../../test/integration/decoders/upc.spec.ts';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import '../../../../test/integration/decoders/pharmacode.spec.ts';
|
||||||
18
quagga2/quagga2-1.12.1/cypress/e2e/universal.cy.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
window.ENV = { development: true, production: false, node: false };
|
||||||
|
|
||||||
|
import '../../src/common/test/array_helper.spec.ts';
|
||||||
|
import '../../src/common/test/area_overlay.spec.ts';
|
||||||
|
import '../../src/common/test/cv_utils.spec';
|
||||||
|
import '../../src/common/test/events.spec.ts';
|
||||||
|
import '../../src/locator/test/barcode_locator.spec.ts';
|
||||||
|
import '../../src/locator/test/skeletonizer.spec.ts';
|
||||||
|
import '../../src/quagga/test/transform.spec.ts';
|
||||||
|
// Tests for Exception class (toJSON serialization)
|
||||||
|
import '../../src/quagga/test/Exception.spec.ts';
|
||||||
|
// Tests for Quagga class (transformResult method)
|
||||||
|
import '../../src/quagga/test/quagga.spec.ts';
|
||||||
|
// Tests for drawScannerArea method
|
||||||
|
import '../../src/quagga/test/drawScannerArea.spec.ts';
|
||||||
|
import '../../src/common/test/image_wrapper.spec.ts';
|
||||||
|
// Tests for QuaggaJSStaticInterface (init method)
|
||||||
|
import '../../src/test/quagga.spec.ts';
|
||||||
82
quagga2/quagga2-1.12.1/cypress/plugins/index.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/// <reference types="cypress" />
|
||||||
|
// ***********************************************************
|
||||||
|
// This example plugins/index.js can be used to load plugins
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off loading
|
||||||
|
// the plugins file with the 'pluginsFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/plugins-guide
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// This function is called when a project is opened or re-opened (e.g. due to
|
||||||
|
// the project's config changing)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Cypress.PluginConfig}
|
||||||
|
*/
|
||||||
|
const webpack = require('@cypress/webpack-preprocessor');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = (on, config) => {
|
||||||
|
config.env = config.env || {};
|
||||||
|
config.env.BUILD_ENV = 'production';
|
||||||
|
// Resolve path to the temporary bundle test fixture
|
||||||
|
const bundleFixturePath = path.resolve((config.projectRoot || process.cwd()), 'cypress/fixtures/bundle-test.html');
|
||||||
|
|
||||||
|
// Expose cleanup tasks to the Cypress runner
|
||||||
|
on('task', {
|
||||||
|
deleteFile(filePath) {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
// ENOENT is fine; any other error bubble up for visibility
|
||||||
|
if (err && err.code === 'ENOENT') return true;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cleanupBundleFixture() {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(bundleFixturePath)) {
|
||||||
|
fs.unlinkSync(bundleFixturePath);
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// Ignore errors during cleanup
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
fileExists(filePath) {
|
||||||
|
return fs.existsSync(filePath);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Best-effort cleanup on process exit/interrupt, guarded to avoid duplicate handlers
|
||||||
|
if (!global.__bundleCleanupHandlersRegistered) {
|
||||||
|
const safeUnlink = () => {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(bundleFixturePath)) {
|
||||||
|
fs.unlinkSync(bundleFixturePath);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
};
|
||||||
|
process.on('exit', safeUnlink);
|
||||||
|
process.on('SIGINT', () => { safeUnlink(); process.exit(1); });
|
||||||
|
process.on('SIGTERM', () => { safeUnlink(); process.exit(1);});
|
||||||
|
global.__bundleCleanupHandlersRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development') {
|
||||||
|
const webpackOptions = {
|
||||||
|
webpackOptions: require('../../configs/webpack.config'),
|
||||||
|
watchOptions: {},
|
||||||
|
};
|
||||||
|
on('file:preprocessor', webpack(webpackOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
// on('file:preprocessor', require('@cypress/code-coverage/use-babelrc'));
|
||||||
|
return config;
|
||||||
|
};
|
||||||
25
quagga2/quagga2-1.12.1/cypress/support/commands.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// ***********************************************
|
||||||
|
// This example commands.js shows you how to
|
||||||
|
// create various custom commands and overwrite
|
||||||
|
// existing commands.
|
||||||
|
//
|
||||||
|
// For more comprehensive examples of custom
|
||||||
|
// commands please read more here:
|
||||||
|
// https://on.cypress.io/custom-commands
|
||||||
|
// ***********************************************
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a parent command --
|
||||||
|
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a child command --
|
||||||
|
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a dual command --
|
||||||
|
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This will overwrite an existing command --
|
||||||
|
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||||
21
quagga2/quagga2-1.12.1/cypress/support/e2e.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// ***********************************************************
|
||||||
|
// This example support/index.js is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
import './commands';
|
||||||
|
import '@cypress/code-coverage/support';
|
||||||
|
|
||||||
|
// Alternatively you can use CommonJS syntax:
|
||||||
|
// require('./commands')
|
||||||
6
quagga2/quagga2-1.12.1/cypress/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["cypress"]
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
quagga2/quagga2-1.12.1/doc/img/bb-binary.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
quagga2/quagga2-1.12.1/doc/img/bb-rotated.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
quagga2/quagga2-1.12.1/doc/img/binary.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
quagga2/quagga2-1.12.1/doc/img/component-labeling-line.png
Normal file
|
After Width: | Height: | Size: 672 B |
BIN
quagga2/quagga2-1.12.1/doc/img/component-labeling-text.png
Normal file
|
After Width: | Height: | Size: 926 B |
BIN
quagga2/quagga2-1.12.1/doc/img/component-labeling.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
quagga2/quagga2-1.12.1/doc/img/connected-patch-labels.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
quagga2/quagga2-1.12.1/doc/img/mobile-detected.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
quagga2/quagga2-1.12.1/doc/img/mobile-located.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
quagga2/quagga2-1.12.1/doc/img/patches_found.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
quagga2/quagga2-1.12.1/doc/img/quaggaJS-code128.png
Normal file
|
After Width: | Height: | Size: 357 B |
BIN
quagga2/quagga2-1.12.1/doc/img/remaining-patch-labels.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
quagga2/quagga2-1.12.1/doc/img/skeleton.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
quagga2/quagga2-1.12.1/doc/img/teaser.png
Normal file
|
After Width: | Height: | Size: 405 KiB |
169207
quagga2/quagga2-1.12.1/doc/profiles/skel_asm.json
Normal file
167
quagga2/quagga2-1.12.1/doc/readme.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
Barcode Reader
|
||||||
|
==============
|
||||||
|
|
||||||
|
Barcode Reader is a library written in JavaScript capable of decoding common barcodes (e.g.: Code128 and EAN13) directly in the browser. It can handle various sources of images such as file-input for a single image or preferrably a camera stream (via `getUserMedia`) for real-time decoding.
|
||||||
|
|
||||||
|
# How does it work?
|
||||||
|
For those of you interested in the inner workings of this implementation, please keep on reading.
|
||||||
|
|
||||||
|
As already depicted in the image above, this implementation makes use of two subsequent stages, first locating (blue box) and second decoding (red line) the barcode.
|
||||||
|
|
||||||
|
## Barcode Locator
|
||||||
|
The main purpose of the barcode locator is finding a pattern within the source image which looks like a barcode. A barcode is typically characterized by its black bars and white gaps in between. The overall size of the barcode may depend on the amount of information encoded (Code128) or be fixed in width (EAN13). When searching for a barcode in an image we are looking for:
|
||||||
|
|
||||||
|
- _lines_, which are
|
||||||
|
- _close_ _to_ each other
|
||||||
|
- have a similar _angle_
|
||||||
|
|
||||||
|
The process of locating such a barcode is loosely based on a paper called [Locating and decoding EAN-13 barcodes from images captured by digital cameras][douglas_05] which's steps were adapted and modified resulting in:
|
||||||
|
|
||||||
|
1. Creating a binary representation of the image
|
||||||
|
2. Slice the image into a grid (20 x 15 cells)
|
||||||
|
3. Extract the skeleton of each cell
|
||||||
|
4. Separate the skeleton into its parts
|
||||||
|
5. Component labeling
|
||||||
|
6. Calculate the rotation of each part
|
||||||
|
7. Determine cell quality (rotation uniformity)
|
||||||
|
8. Find connecting cells with similar rotation
|
||||||
|
9. Create bounding-box of connected cells
|
||||||
|
|
||||||
|
Let's start with the creation of a binary image, or more precisely, a thresholded image.
|
||||||
|
|
||||||
|
### Creating a binary image
|
||||||
|
There are many different ways of creating a binary representation of an image. The easiest being a global threshold of 127 and determining for each pixel if its brighter (>=127) or darker(<127) than the threshold. Whilst this method might be the simpliest to implement, the outcome might not be sufficient because it does not take into account illumination changes across the image.
|
||||||
|
|
||||||
|
In order to account for the brightness changes in the image, a method called [Otsu][otsu_wiki] is employed which operates on the histogram of the source image and tries to separate the foreground from the background. The image below is produced by exactly this method.
|
||||||
|
|
||||||
|
![Binary Image][binary_image]
|
||||||
|
|
||||||
|
The separation of the barcode from the background works fairly well, resulting in an image which is handed over to the next stage.
|
||||||
|
|
||||||
|
### Slicing the image into a grid
|
||||||
|
The binary image as a whole does not provide much information about its content. In order to make sense of the contained structure, the image is breaken down into smaller chunks which are then described independently from each other. This _description_ is then compared with our hypotheses and either taken into consideration for further processing, or discarded.
|
||||||
|
|
||||||
|
During the development, a grid of 20 x 15 cells (assuming a 4:3 aspect ratio of the image) yielded to statisfactory results.
|
||||||
|
|
||||||
|
IMG
|
||||||
|
|
||||||
|
Having the image sliced up into individual parts, each cell in the grid needs to be evaluated and classified based on the properties described above. Each cell has to be checked if it contains nothing but parallel lines to get passed on to the next stage.
|
||||||
|
|
||||||
|
First, the bars have to be normalized in width (1px width), then separated line by line, followed by the estimation of the angle of each line. Then, each line in the cell is compared to the others to determine the parallelity. Finally, if the majority of lines are parallel to each other, the average angle is computed.
|
||||||
|
|
||||||
|
### Extract skeleton of each cell
|
||||||
|
The normalization of the width to 1px is done by a method known as [skeletonizing][skeleton_wiki]. What it does is, that it tries to remove as much of the weight of the bar as possible. In this implementation, this is simply done by eroding and dilating the contents as long as there are unprocessed pixels. This yields to a _skeleton_ of the given image as shown in the next image:
|
||||||
|
|
||||||
|
![skeleton_image][skeleton_image]
|
||||||
|
|
||||||
|
This clearly shows the location of the barcode, and the bars of the code reduced to their minimum width of 1px.
|
||||||
|
|
||||||
|
### Component Labeling
|
||||||
|
In order to find out if parallel lines are contained within each cell, the skeletonized content, which ideally contains straight lines of the interested area, must be separated into individual pieces (lines). This is achieved by applying a technique called [connected-component labeling][labeling_wiki] which separates a given region into its individual components. In case of the barcode pattern, all lines within the cell are split up into single lines and then weighted by their rotation.
|
||||||
|
|
||||||
|
Due to the fact that component labeling is usually quite expensive in terms of computation, a fast algorithm was of the essence to create a real-time application. With that in mind, an implementation of the paper ["A Linear-Time Component-Labeling Algorithm Using Contour Tracing Technique"][labeling_paper] by Fu Chang, Chun-Jen Chen, and Chi-Jen Lu was found on [CodeProject][labeling_codeproject]. Unfortunatelly, the original code was written in C and had to be ported to JavaScript.
|
||||||
|
|
||||||
|
The image below visualizes the output of the component-labeling stage.
|
||||||
|
|
||||||
|
![component_labeling_image][component_labeling_image]
|
||||||
|
|
||||||
|
As mentioned before, each cell is treated invividually, that's why each color is used repeatedly throughout the cells. The color for each component within the cell is unique and denotes the label given by the algorithm. The following images show scaled up versions of two extracted cells, of which the left one indicates a possible barcode area whereas the right one does not contain much but noise.
|
||||||
|
|
||||||
|
![Component labeling lines][component_labeling_lines_image]
|
||||||
|
![Component labeling lines][component_labeling_text_image]
|
||||||
|
|
||||||
|
Each label can then be considered as a possible __bar__ of a barcode pattern. To be able to classify such a representation, a quantitative value needs to be computed for each __bar__ which can then be compared with the other components.
|
||||||
|
|
||||||
|
### Determining the orientation of such a component
|
||||||
|
The orientation of a single component within a cell is calculated by using [central image moments][central_image_moments_wiki]. This method is typically used to extract information of the orientation of a binary image. In this case, the binary image is represented by the labeled components. Each component is considered its own binary image of which the central image moment can be computed.
|
||||||
|
|
||||||
|
As depicted in the equation below, the orientation (indicated as θ) of a binary image can be determined by knowing its central moments (μ).
|
||||||
|
|
||||||
|
![Calculation of Theta][math_theta]
|
||||||
|
|
||||||
|
The central moments (μ) are computed by making use of the raw moments (M) and the centroid (x,y) which in turn need to be calculated up front.
|
||||||
|
|
||||||
|
![Calculation of mu][math_mu]
|
||||||
|
|
||||||
|
The following computes the components of the centroid (x,y) by using the raw moments (M).
|
||||||
|
|
||||||
|
![Calculating x bar][math_x_y_bar]
|
||||||
|
|
||||||
|
Since we need image moments up to the second order, the following listing shows the computation of each single moment. The sum over x and y denotes an iteration over the entire image, whereas I(x,y) indicates the value of the pixel at the position x,y. In this case, the value can either be 0 or 1, since we are operating on a binary image.
|
||||||
|
|
||||||
|
![Calculating M][math_m]
|
||||||
|
|
||||||
|
After this step, each component has an angle assigned which is then used in the first step of the classification.
|
||||||
|
|
||||||
|
### Determining cell quality
|
||||||
|
|
||||||
|
#### Discarding non-representative information
|
||||||
|
Cells containing none, or just one component are immediatelly discarded and not used for further processing. In addition, components which do not cover at least 6 px (M00) are exlucded from any subsequent calculations of the affected cell. This pre-filtered list serves as basis for determining the uniformity of the component's angles throughout a single cell.
|
||||||
|
|
||||||
|
In case of a barcode each component in a cell should be ideally parallel to each other. But due to noise, distortion and other influences, this may vary at some degree. A simple clustering technique is applied to find out the similarity of such components. First off, all angles are clustered with a certain threshold, whereupon the cluster with the highest count of members is selected. Only if this cluster's member-size is greater that 3/4 th of the initial set (without the non-representative components) this cell is finally considered as being part of a barcode pattern. From now on, this cell is referred to as a patch, which contains the following information:
|
||||||
|
|
||||||
|
- an index (unique identifier within the grid)
|
||||||
|
- the bounding box defining the cell
|
||||||
|
- all components and their associated moments
|
||||||
|
- an average angle (computed from the cluster)
|
||||||
|
- a vector pointing in the direction of orientation
|
||||||
|
|
||||||
|
The following picture highlights the patches (cells) which were found during the classification process described above.
|
||||||
|
|
||||||
|
![Image found patches][found_patches_image]
|
||||||
|
|
||||||
|
It's noticable that some cells were falsely classified as being part of a barcode pattern. Those false positives can be taken care of by finding connected areas (consisting of more than one cell) and discarding all remaining cells.
|
||||||
|
|
||||||
|
### Finding connected cells
|
||||||
|
Basically, cells can be considered part of a barcode if they are neighbors and share common properties. This grouping is archived by a simple recursive component-labeling algorithm which operates on similarity of orientation, instead of color. For a patch to become member of a label, its vector must point in the same direction as its neighbor's. In order to account for deviations caused by distortion and other geometric influences, the orientation can deviate up to 5 %. The next image illustrates those connected cells where the color defines a certain group.
|
||||||
|
|
||||||
|
![Connected patch labels][connected_patch_labels]
|
||||||
|
|
||||||
|
Sometimes even neighboring cells are colored differently. This happens if the orientation of those patches differ too much (> 5%).
|
||||||
|
|
||||||
|
### Selecting groups
|
||||||
|
Next up is the selection of the groups which most probably contain a barcode. Because more than one barcode can be present in an image at the same time, groups are first sorted and then filtered by their count of patches.
|
||||||
|
|
||||||
|
![Remaining patch labels][remaining_patch_labels]
|
||||||
|
|
||||||
|
Only the biggest groups remain which are then passed on to the creation of a bounding-box.
|
||||||
|
|
||||||
|
### Create bounding box
|
||||||
|
Finally all the information necessary for outlining the location of the barcode is available. In this last step, a minimum bounding box is created
|
||||||
|
which spans over all the patches in one group. First, the average angle of the containing patches is calculated which is then used to rotate the cells
|
||||||
|
by exactly this angle. After that a bounding-box is computed by simply finding the outermost corners of all the patches.
|
||||||
|
|
||||||
|
![Rotated cells with box][transformed_patches]
|
||||||
|
|
||||||
|
Finally, the bounding-box is rotated in the inverse direction to transform it back to its origin.
|
||||||
|
|
||||||
|
![Bounding box][bounding_box_skeleton]
|
||||||
|
|
||||||
|
As illustrated in the image above, the bounding-box is placed exactly where the barcode pattern is printed. Having a box like this helps a lot during the
|
||||||
|
actual decoding process because knowing the orientation and the rough area limits the scanning efforts.
|
||||||
|
|
||||||
|
## Barcode Decoder
|
||||||
|
|
||||||
|
|
||||||
|
[teaser_image]: https://www.oberhofer.co/content/images/2014/11//teaser.png
|
||||||
|
[binary_image]: https://www.oberhofer.co/content/images/2014/11//binary.png
|
||||||
|
[skeleton_image]: https://www.oberhofer.co/content/images/2014/11//skeleton.png
|
||||||
|
[otsu_wiki]: http://en.wikipedia.org/wiki/Otsu%27s_method
|
||||||
|
[douglas_05]: http://www.icics.org/2005/download/P0840.pdf
|
||||||
|
[skeleton_wiki]: http://en.wikipedia.org/wiki/Morphological_skeleton
|
||||||
|
[labeling_wiki]: http://en.wikipedia.org/wiki/Connected-component_labeling
|
||||||
|
[labeling_codeproject]: http://www.codeproject.com/Tips/407172/Connected-Component-Labeling-and-Vectorization
|
||||||
|
[labeling_paper]: http://www.iis.sinica.edu.tw/papers/fchang/1362-F.pdf
|
||||||
|
[component_labeling_image]: https://www.oberhofer.co/content/images/2014/11//component-labeling.png
|
||||||
|
[component_labeling_lines_image]: https://www.oberhofer.co/content/images/2014/11//component-labeling-line.png
|
||||||
|
[component_labeling_text_image]: https://www.oberhofer.co/content/images/2014/11//component-labeling-text.png
|
||||||
|
[math_theta]: 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
|
||||||
|
[math_mu]: 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
|
||||||
|
[math_x_y_bar]: 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
|
||||||
|
[math_m]: 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
|
||||||
|
[central_image_moments_wiki]: http://en.wikipedia.org/wiki/Image_moment#Central_moments
|
||||||
|
[found_patches_image]: https://www.oberhofer.co/content/images/2014/11//patches_found.png
|
||||||
|
[connected_patch_labels]: https://www.oberhofer.co/content/images/2014/11//connected-patch-labels.png
|
||||||
|
[remaining_patch_labels]: https://www.oberhofer.co/content/images/2014/11//remaining-patch-labels.png
|
||||||
|
[transformed_patches]: https://www.oberhofer.co/content/images/2014/11//bb-rotated.png
|
||||||
|
[bounding_box_skeleton]: https://www.oberhofer.co/content/images/2014/11//bb-binary.png
|
||||||
10
quagga2/quagga2-1.12.1/docker-compose.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
version: '3.7'
|
||||||
|
|
||||||
|
services:
|
||||||
|
nodejs:
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
image: quagga2/build
|
||||||
|
volumes:
|
||||||
|
- .:/quagga2
|
||||||
|
command: "npm run build"
|
||||||
3
quagga2/quagga2-1.12.1/docs/_config.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
title: Quagga2 Documentation
|
||||||
|
description: Barcode scanning for browser and node!
|
||||||
|
theme: jekyll-theme-cayman
|
||||||
BIN
quagga2/quagga2-1.12.1/docs/assets/bb-binary.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
quagga2/quagga2-1.12.1/docs/assets/bb-rotated.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
quagga2/quagga2-1.12.1/docs/assets/binary.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
quagga2/quagga2-1.12.1/docs/assets/component-labeling-line.png
Normal file
|
After Width: | Height: | Size: 672 B |
BIN
quagga2/quagga2-1.12.1/docs/assets/component-labeling-text.png
Normal file
|
After Width: | Height: | Size: 926 B |
BIN
quagga2/quagga2-1.12.1/docs/assets/component-labeling.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
quagga2/quagga2-1.12.1/docs/assets/connected-patch-labels.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
quagga2/quagga2-1.12.1/docs/assets/mobile-detected.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
quagga2/quagga2-1.12.1/docs/assets/mobile-located.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
quagga2/quagga2-1.12.1/docs/assets/patches_found.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
quagga2/quagga2-1.12.1/docs/assets/quaggaJS-code128.png
Normal file
|
After Width: | Height: | Size: 357 B |
BIN
quagga2/quagga2-1.12.1/docs/assets/remaining-patch-labels.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
quagga2/quagga2-1.12.1/docs/assets/skeleton.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
quagga2/quagga2-1.12.1/docs/assets/teaser.png
Normal file
|
After Width: | Height: | Size: 405 KiB |
119
quagga2/quagga2-1.12.1/docs/contributing.md
Normal 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.
|
||||||
7
quagga2/quagga2-1.12.1/docs/examples/.eslintrc
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
86
quagga2/quagga2-1.12.1/docs/examples/camera_example.html
Normal 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 © 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>
|
||||||
27
quagga2/quagga2-1.12.1/docs/examples/config.rb
Normal 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
|
||||||
16
quagga2/quagga2-1.12.1/docs/examples/css/colors.css
Normal 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 */
|
||||||
1
quagga2/quagga2-1.12.1/docs/examples/css/fonts.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@import url("https://fonts.googleapis.com/css?family=Ubuntu:400,700|Cabin+Condensed:400,600");
|
||||||
298
quagga2/quagga2-1.12.1/docs/examples/css/styles.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
110
quagga2/quagga2-1.12.1/docs/examples/file_input.html
Normal 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 © 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>
|
||||||
203
quagga2/quagga2-1.12.1/docs/examples/file_input.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 13 KiB |
BIN
quagga2/quagga2-1.12.1/docs/examples/fixtures/ean/image-001.jpg
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
quagga2/quagga2-1.12.1/docs/examples/fixtures/ean/image-002.jpg
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
quagga2/quagga2-1.12.1/docs/examples/fixtures/ean/image-003.jpg
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
quagga2/quagga2-1.12.1/docs/examples/fixtures/ean/image-004.jpg
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
quagga2/quagga2-1.12.1/docs/examples/fixtures/ean/image-005.jpg
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
quagga2/quagga2-1.12.1/docs/examples/fixtures/ean/image-006.jpg
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
quagga2/quagga2-1.12.1/docs/examples/fixtures/ean/image-007.jpg
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
quagga2/quagga2-1.12.1/docs/examples/fixtures/ean/image-008.jpg
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
quagga2/quagga2-1.12.1/docs/examples/fixtures/ean/image-009.jpg
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
quagga2/quagga2-1.12.1/docs/examples/fixtures/ean/image-010.jpg
Normal file
|
After Width: | Height: | Size: 98 KiB |
31
quagga2/quagga2-1.12.1/docs/examples/index.html
Normal 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 © 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>
|
||||||
116
quagga2/quagga2-1.12.1/docs/examples/live_w_locator.html
Normal 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>
|
||||||
|
© Made with ❤️ by Christoph Oberhofer
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Quagga2 Project © 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>
|
||||||
402
quagga2/quagga2-1.12.1/docs/examples/live_w_locator.js
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
21
quagga2/quagga2-1.12.1/docs/examples/node-test.js
Normal 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");
|
||||||
|
}
|
||||||
|
});
|
||||||
70
quagga2/quagga2-1.12.1/docs/examples/static_images.html
Normal 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 © 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>
|
||||||
177
quagga2/quagga2-1.12.1/docs/examples/static_images.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||