Converted web app to react + js

This commit is contained in:
2026-03-27 07:47:39 +00:00
parent 5b31525fbc
commit b2f34e308e
434 changed files with 3218 additions and 225894 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules
dist

View File

@@ -1,157 +0,0 @@
// barcode-scanner.js - Barcode scanning module with console logging for testing
/**
* Initialize barcode scanner with dual input support:
* - Keyboard input (simulation)
* - Hardware barcode scanner devices
*
* Logs scanned barcodes to console for testing purposes
*/
let barcodeBuffer = '';
let lastBarcodeTime = 0;
const SCANNER_TIMEOUT = 100; // ms - detect hardware scanner vs human typing
/**
* Log barcode to console with metadata
* @param {string} barcode - The scanned barcode value
* @param {string} inputType - Either 'keyboard' or 'hardware-scanner'
*/
export function logBarcodeToConsole(barcode, inputType = 'keyboard') {
const timestamp = new Date().toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
fractionalSecondDigits: 3
});
console.log(
`%c[Barcode Scanned] %c${timestamp} | %cBarcode: %c${barcode} %c| Input: %c${inputType}`,
'color: #4CAF50; font-weight: bold;',
'color: #666; font-family: monospace;',
'color: #333; font-weight: bold;',
'color: #2196F3; font-weight: bold; font-family: monospace;',
'color: #666;',
'color: #FF9800; font-weight: bold;'
);
// Additional metadata for debugging
console.log({
barcode,
timestamp: new Date().toISOString(),
inputType,
length: barcode.length
});
}
/**
* Detect if input is from a hardware scanner or keyboard
* Hardware scanners typically input a full barcode very quickly (within SCANNER_TIMEOUT ms)
* @returns {string} - 'hardware-scanner' or 'keyboard'
*/
function detectInputType(currentTime) {
const timeSinceLastInput = currentTime - lastBarcodeTime;
lastBarcodeTime = currentTime;
// If more than SCANNER_TIMEOUT ms since last character, it's likely keyboard input
return timeSinceLastInput > SCANNER_TIMEOUT ? 'keyboard' : 'hardware-scanner';
}
/**
* Handle barcode input from both keyboard and hardware scanner
* @param {HTMLElement} inputElement - The input field element
* @param {Function} onBarcodeScanned - Callback function when barcode is complete
*/
export function initializeBarcodeScanner(inputElement, onBarcodeScanned) {
if (!inputElement) {
console.error('Barcode input element not found');
return;
}
// Auto-focus for seamless scanning
inputElement.focus();
inputElement.addEventListener('keypress', (event) => {
// Most barcode scanners send Enter key at the end
if (event.key === 'Enter') {
event.preventDefault();
const barcode = inputElement.value.trim();
if (barcode) {
// Detect input type
const inputType = barcodeBuffer.length === 0 ? 'keyboard' : 'hardware-scanner';
barcodeBuffer = '';
// Log to console
logBarcodeToConsole(barcode, inputType);
// Call the callback function
if (onBarcodeScanned) {
onBarcodeScanned(barcode, inputType);
}
}
// Clear input for next scan
inputElement.value = '';
inputElement.focus();
} else {
// Accumulate characters in buffer for input type detection
const now = Date.now();
if (barcodeBuffer === '') {
lastBarcodeTime = now;
}
barcodeBuffer += event.key;
}
});
// Handle pasted input (in case user pastes barcode)
inputElement.addEventListener('paste', (event) => {
event.preventDefault();
const pastedText = (event.clipboardData || window.clipboardData).getData('text').trim();
if (pastedText) {
logBarcodeToConsole(pastedText, 'keyboard-paste');
if (onBarcodeScanned) {
onBarcodeScanned(pastedText, 'keyboard-paste');
}
}
inputElement.value = '';
inputElement.focus();
});
// Maintain focus
inputElement.addEventListener('blur', () => {
setTimeout(() => inputElement.focus(), 0);
});
console.log('%cBarcode Scanner Initialized', 'color: #4CAF50; font-weight: bold;');
console.log('Ready to scan. Focus the input field and scan a barcode or press Enter to complete.');
}
/**
* Get scanning statistics from console (for testing)
* This is just a helper for development/testing
* @returns {Object} - Statistics object
*/
export function getScanningStats() {
return {
initialized: true,
timestamp: new Date().toISOString(),
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');
}

View File

@@ -1,615 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="main.css">
<title>Barcode Scanner</title>
<script src="./quagga2/quagga2-1.12.1/docs/examples/dist/quagga.min.js"></script>
<style>
.scanner-container {
background: #f9f9f9;
padding: 30px;
border-radius: 8px;
max-width: 800px;
margin: 30px auto;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.scanner-header {
text-align: center;
margin-bottom: 30px;
}
.scanner-header h3 {
color: #333;
margin-top: 0;
}
.scanner-header p {
color: #666;
font-size: 14px;
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 {
margin-bottom: 20px;
}
.barcode-input-wrapper label {
display: block;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.barcode-input {
width: 100%;
padding: 15px;
border: 2px solid #ddd;
border-radius: 4px;
font-size: 18px;
font-family: 'Courier New', monospace;
box-sizing: border-box;
transition: all 0.3s;
}
.barcode-input:focus {
outline: none;
border-color: #4CAF50;
box-shadow: 0 0 8px rgba(76, 175, 80, 0.3);
background-color: #fffef0;
}
.barcode-input.highlight {
border-color: #4CAF50;
background-color: #e8f5e9;
}
.scanner-status {
text-align: center;
padding: 15px;
border-radius: 4px;
background-color: #e8f5e9;
border: 1px solid #c8e6c9;
color: #2e7d32;
font-size: 14px;
margin-bottom: 20px;
}
.scanner-status.active {
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% {
background-color: #e8f5e9;
}
50% {
background-color: #c8e6c9;
}
}
.scanner-info {
background-color: #e3f2fd;
border: 1px solid #bbdefb;
border-radius: 4px;
padding: 15px;
margin-top: 20px;
font-size: 13px;
color: #1565c0;
}
.scanner-info h4 {
margin: 0 0 10px 0;
color: #0d47a1;
}
.scanner-info ul {
margin: 10px 0 0 0;
padding-left: 20px;
}
.scanner-info li {
margin: 5px 0;
line-height: 1.5;
}
.console-hint {
background-color: #fff3e0;
border: 1px solid #ffe0b2;
border-radius: 4px;
padding: 12px;
margin-top: 15px;
font-size: 13px;
color: #e65100;
}
.console-hint strong {
display: block;
margin-bottom: 5px;
color: #d84315;
}
.button-group {
display: flex;
gap: 10px;
margin-top: 20px;
flex-wrap: wrap;
}
.btn {
padding: 12px 20px;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
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 {
background-color: #ddd;
color: #333;
}
.btn-secondary:hover {
background-color: #bbb;
}
.btn-danger {
background-color: #f44336;
color: white;
}
.btn-danger:hover {
background-color: #da190b;
}
.open-console {
background-color: #2196F3;
color: white;
}
.open-console:hover {
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 */
@media (max-width: 600px) {
.scanner-container {
padding: 20px;
margin: 20px 10px;
}
.barcode-input {
font-size: 16px;
padding: 12px;
}
.button-group {
flex-direction: column;
}
.btn {
width: 100%;
}
.mode-toggle {
flex-wrap: wrap;
}
.mode-btn {
flex: 1;
min-width: 120px;
}
}
</style>
</head>
<body>
<div id="navbar-placeholder"></div>
<div id="page-content">
<h2>Barcode Scanner</h2>
<hr>
<div class="scanner-container">
<div class="scanner-header">
<h3>📱 Barcode Scanner</h3>
<p>Scan barcodes using camera or keyboard input</p>
</div>
<!-- Mode Toggle -->
<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>
<!-- Error Message -->
<div class="error-message" id="error-message"></div>
<!-- Keyboard Input Section -->
<div class="keyboard-section" id="keyboard-section">
<div class="scanner-status active" id="scanner-status">
✓ Scanner Ready - Click below and scan a barcode
</div>
<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>
<!-- Camera Section -->
<div class="camera-section" id="camera-section">
<div class="scanner-status" id="camera-status">
📷 Camera Ready - Click "Start Camera" to begin scanning
</div>
<video id="video" width="800" height="600"></video>
<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 class="console-hint">
<strong>🔍 View Console Output:</strong>
Press F12 (or Cmd+Option+I on Mac) to open Developer Tools. Check the Console tab to see scanned barcodes logged with timestamps and input type detection.
</div>
<div class="button-group">
<button type="button" class="btn open-console" id="open-console-btn">
📟 Open Console (F12)
</button>
<button type="button" class="btn btn-secondary" id="clear-input-btn">
🗑️ Clear Input
</button>
</div>
</div>
</div>
<script src="navbar.js"></script>
<script type="module">
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 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 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
*/
function onBarcodeScanned(barcode, inputType) {
if (inputType === 'camera') {
// Camera detection feedback
detectedBarcodeValue.textContent = barcode;
detectedBarcodeDisplay.classList.add('show');
cameraStatus.textContent = `✓ Barcode detected: ${barcode}`;
cameraStatus.style.backgroundColor = '#a5d6a7';
setTimeout(() => {
cameraStatus.textContent = '📷 Scanning...';
cameraStatus.style.backgroundColor = '';
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);
}
}
/**
* 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
clearInputBtn.addEventListener('click', () => {
barcodeInput.value = '';
barcodeInput.focus();
});
// Open console button
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;');
alert('Press F12 (or Cmd+Option+I on Mac) to open the Developer Tools Console.\n\nScanned barcodes will appear in the Console tab.');
});
// Log initial message
console.log('%c🎯 Barcode Scanner Ready', 'color: #4CAF50; font-weight: bold; font-size: 16px;');
console.log('Scan a barcode to see it logged here with timestamp and input type detection.');
</script>
</body>
</html>

View File

@@ -1,47 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<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"> <title>Webpage Playground</title>
<title>Homepage</title> </head>
</head> <body>
<body> <div id="root"></div>
<div id="navbar-placeholder"></div> <script type="module" src="/src/main.jsx"></script>
<div id="page-content"> </body>
<h2>Homepage</h2> </html>
<hr>
<h3>To use</h3>
<div id="item-component-demo"></div>
<script type="module">
console.log('Demo script running');
import createItemComponent from './item-component.js';
const demoRoot = document.getElementById('item-component-demo');
demoRoot.className = 'item-component-grid';
const items = [
{ imgSrc: 'https://picsum.photos/seed/1/200/200', imgAlt: 'Chicken Salad', text1: 'Chicken Salad', text2: 'Fresh — 2 left', text3: 'Use within 3 days' },
{ imgSrc: 'https://picsum.photos/seed/2/200/200', imgAlt: 'Yogurt', text1: 'Greek Yogurt', text2: 'Chilled — 6 left', text3: 'Best before 5 days' },
{ imgSrc: 'https://picsum.photos/seed/3/200/200', imgAlt: 'Apples', text1: 'Red Apples', text2: 'Room temp — 12 left', text3: 'Keep away from moisture' }, ];
items.forEach(data => {
const comp = createItemComponent({ ...data, editable: false });
demoRoot.appendChild(comp);
});
</script>
<hr>
<h3>Inventory Breakdown</h3>
<div class="chart-container">
<svg width="350" height="350" viewBox="0 0 42 42">
<circle cx="21" cy="21" r="10" fill="transparent" stroke="#f2f2f2" stroke-width="20"></circle>
<circle id="seg-1" cx="21" cy="21" r="10" fill="transparent" stroke-width="20"></circle>
<circle id="seg-2" cx="21" cy="21" r="10" fill="transparent" stroke-width="20"></circle>
<circle id="seg-3" cx="21" cy="21" r="10" fill="transparent" stroke-width="20"></circle>
</svg>
<div id="legend"></div>
</div>
</div>
<script src="navbar.js"></script>
<script src="piechart.js"></script>
</body>
</html>

View File

@@ -1,92 +0,0 @@
// item-component.js
// Reusable item component: image on left, three text areas on right.
const _ensureStyles = (() => {
if (document.getElementById('item-component-styles')) return true;
const style = document.createElement('style');
style.id = 'item-component-styles';
style.textContent = `
/* Grid wrapper for multiple item components */
.item-component-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;align-items:start}
.item-component{display:flex;align-items:flex-start;gap:12px;font-family:inherit;padding:10px;border:1px solid #e6e6e6;border-radius:8px;background:#fff}
.item-component__img{flex:0 0 auto}
.item-component__img img{display:block;width:72px;height:72px;object-fit:cover;border-radius:6px}
.item-component__content{flex:1;display:flex;flex-direction:column;gap:6px}
.item-component__line{margin:0;padding:0;color:#222}
.item-component__line--title{font-weight:700}
.item-component__line--subtitle{color:#555;font-size:0.95em}
.item-component__line--desc{color:#666;font-size:0.9em}
.item-component__textarea{width:100%;box-sizing:border-box;padding:6px;font:inherit;border:1px solid #ccc;border-radius:4px}
/* Responsive: on small screens show 1 or 2 columns */
@media (max-width: 900px){
.item-component-grid{grid-template-columns:repeat(2,1fr)}
}
@media (max-width: 560px){
.item-component-grid{grid-template-columns:repeat(1,1fr)}
}
`;
document.head.appendChild(style);
return true;
})();
/**
* Create an item component element.
* @param {Object} opts
* @param {string} opts.imgSrc - image URL
* @param {string} opts.imgAlt - image alt text
* @param {string} opts.text1 - first text area (title)
* @param {string} opts.text2 - second text area (subtitle)
* @param {string} opts.text3 - third text area (description)
* @param {boolean} opts.editable - if true, text areas are `<textarea>` elements for editing
* @param {number} opts.imgWidth - image width in px
* @param {number} opts.imgHeight - image height in px
* @returns {HTMLElement}
*/
export function createItemComponent({imgSrc = '', imgAlt = '', text1 = '', text2 = '', text3 = '', editable = false, imgWidth = 80, imgHeight = 80} = {}) {
_ensureStyles;
const root = document.createElement('div');
root.className = 'item-component';
// Image container
const imgWrap = document.createElement('div');
imgWrap.className = 'item-component__img';
const img = document.createElement('img');
if (imgSrc) img.src = imgSrc;
img.alt = imgAlt || '';
img.width = imgWidth;
img.height = imgHeight;
imgWrap.appendChild(img);
root.appendChild(imgWrap);
// Content container with three text areas
const content = document.createElement('div');
content.className = 'item-component__content';
function makeTextNode(text, cls) {
if (editable) {
const ta = document.createElement('textarea');
ta.className = 'item-component__textarea ' + cls;
ta.value = text;
return ta;
}
const el = document.createElement('div');
el.className = 'item-component__line ' + cls;
el.textContent = text;
return el;
}
const line1 = makeTextNode(text1, 'item-component__line--title');
const line2 = makeTextNode(text2, 'item-component__line--subtitle');
const line3 = makeTextNode(text3, 'item-component__line--desc');
content.appendChild(line1);
content.appendChild(line2);
content.appendChild(line3);
root.appendChild(content);
return root;
}
export default createItemComponent;

105
main.css
View File

@@ -1,105 +0,0 @@
/* Dynamically Changing the content padding depending on the width of the screen */
@media screen and (min-width: 1921px) and (max-width: 2560px) {
body {
padding-left: 20%;
padding-right: 20%;
}
}
@media screen and (max-width: 1920px) {
body {
padding-left: 10%;
padding-right: 10%;
}
}
/* General Styling */
body{
font-family: Arial, Helvetica, sans-serif;
}
h2 {
text-align: center;
font-size: xx-large;
}
h3 {
text-align: center;
font-size: x-large;
}
hr {
border: 0;
height: 2px;
background: #5b5b5b;
margin: 20px 0;
width: 100%;
}
/* Navbar Styling */
nav ul {
list-style-type: none;
margin: 0;
padding: 0;
background-color: #333333;
display: flex;
justify-content: center;
}
/* Give navbar the same rounded corners as the item components and clip children */
nav ul {
border-radius: 8px;
overflow: hidden;
}
ul li a {
display: block;
color: white;
padding: 14px 16px;
text-decoration: none;
}
ul li a:hover {
background-color: #111111;
}
/* Pie chart styling */
.chart-container {
display: flex;
align-items: center;
justify-content: center;
font-family: sans-serif;
gap: 20px;
}
#pie-segment {
transform: rotate(-90deg);
transform-origin: center;
transition: stroke-dasharray 0.5s ease-in-out;
}
.label h2 {
margin: 0;
color: #3498db;
}
circle {
transition: stroke-dasharray 0.6s ease, stroke-dashoffset 0.6s ease;
transform: rotate(-90deg);
transform-origin: center;
}
#legend {
font-family: sans-serif;
display: flex;
flex-direction: column;
gap: 8px;
}
.legend-item {
display: flex;
align-items: center;
gap: 10px;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
}

View File

@@ -1,16 +0,0 @@
function createNavbar() {
const navbarHTML = `
<nav>
<ul>
<li><a href="index.html" data-route="index.html">Homepage</a></li>
<li><a href="search.html" data-route="search.html">Search</a></li>
<li><a href="barcode.html" data-route="barcode.html">Barcode Scanner</a></li>
</ul>
</nav>
`;
const navbarContainer = document.getElementById('navbar-placeholder');
if (navbarContainer) {
navbarContainer.innerHTML = navbarHTML;
}
}
createNavbar();

1687
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

28
package.json Normal file
View File

@@ -0,0 +1,28 @@
{
"name": "webpage-playground",
"version": "1.0.0",
"description": "A modern inventory management demo app featuring multiple pages for browsing, searching, and scanning barcodes. Built with vanilla HTML, CSS, and JavaScript.",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"repository": {
"type": "git",
"url": "https://git.betteridge.net/ldbetteridge/pantry-management-frontend.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@ericblade/quagga2": "^1.12.1",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router-dom": "^7.13.2"
},
"devDependencies": {
"@vitejs/plugin-react": "^6.0.1",
"vite": "^8.0.3"
}
}

View File

@@ -1,48 +0,0 @@
const segments = [
{ label: "Pantry", value: 104, color: getRandomColor() },
{ label: "Fridge", value: 30, color: getRandomColor() },
{ label: "Freezer", value: 87, color: getRandomColor() }
];
const totalValue = segments.reduce((accumulator, current) => {
return accumulator + current.value;
}, 0);
const circumference = 2 * Math.PI * 10;
function renderPieChart(data) {
let currentOffset = 0;
data.forEach((item, index) => {
const fraction = item.value / totalValue;
const segmentLength = fraction * circumference;
const circle = document.getElementById(`seg-${index + 1}`);
circle.style.strokeDasharray = `${segmentLength} ${circumference}`;
circle.style.strokeDashoffset = -currentOffset;
circle.style.stroke = item.color;
currentOffset += segmentLength;
addLegendItem(item);
});
}
function addLegendItem(item) {
const legend = document.getElementById('legend');
const div = document.createElement('div');
div.className = 'legend-item';
div.innerHTML = `
<span class="dot" style="background:${item.color}"></span>
<span>${item.label}: <strong>${item.value}</strong></span>
`;
legend.appendChild(div);
}
function getRandomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 10)];
}
return color;
}
renderPieChart(segments);

View File

@@ -1,26 +0,0 @@
{
"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"
]
}
}
}

View File

@@ -1,74 +0,0 @@
{
"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"
}
}

View File

@@ -1,11 +0,0 @@
.sass-cache/
node_modules/
coverage/
.project
_site/
.idea/
.vscode/
dist/
lib/quagga.js
cypress/videos
.nyc_output

View File

@@ -1,18 +0,0 @@
{
"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"
]
}

View File

@@ -1,37 +0,0 @@
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

View File

@@ -1,4 +0,0 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.13.5
ignore: {}
patch: {}

View File

@@ -1,338 +0,0 @@
# 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

View File

@@ -1,7 +0,0 @@
ARG NODE_VERSION=13
FROM node:${NODE_VERSION}-alpine
WORKDIR /quagga2
USER node

View File

@@ -1,178 +0,0 @@
# 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.

View File

@@ -1,21 +0,0 @@
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.

View File

@@ -1,997 +0,0 @@
# quagga2
[![Rolling Versions](https://img.shields.io/badge/Rolling%20Versions-Enabled-brightgreen)](https://rollingversions.com/ericblade/quagga2)
[![Join the chat at https://gitter.im/quaggaJS/Lobby](https://badges.gitter.im/quaggaJS/Lobby.svg)](https://gitter.im/quaggaJS/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Rate on Openbase](https://badges.openbase.io/js/rating/@ericblade/quagga2.svg)](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.

View File

@@ -1,5 +0,0 @@
module.exports = {
production: false,
development: true,
node: false,
};

View File

@@ -1,5 +0,0 @@
module.exports = {
production: true,
development: false,
node: true,
};

View File

@@ -1,5 +0,0 @@
module.exports = {
production: true,
development: false,
node: false,
};

View File

@@ -1,78 +0,0 @@
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',
};

View File

@@ -1,15 +0,0 @@
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;

View File

@@ -1,36 +0,0 @@
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';

View File

@@ -1,14 +0,0 @@
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)
},
},
})

View File

@@ -1,62 +0,0 @@
// 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'));
}
});
});
});
});
});

View File

@@ -1,11 +0,0 @@
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';

View File

@@ -1,156 +0,0 @@
// 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');
});
});
});

View File

@@ -1,22 +0,0 @@
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';

View File

@@ -1 +0,0 @@
import '../../../../test/integration/decoders/pharmacode.spec.ts';

View File

@@ -1,18 +0,0 @@
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';

View File

@@ -1,82 +0,0 @@
/// <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;
};

View File

@@ -1,25 +0,0 @@
// ***********************************************
// 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) => { ... })

View File

@@ -1,21 +0,0 @@
// ***********************************************************
// 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')

View File

@@ -1,6 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["cypress"]
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 KiB

File diff suppressed because one or more lines are too long

View File

@@ -1,167 +0,0 @@
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 &theta;) of a binary image can be determined by knowing its central moments (&mu;).
![Calculation of Theta][math_theta]
The central moments (&mu;) 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

View File

@@ -1,10 +0,0 @@
version: '3.7'
services:
nodejs:
build:
context: ./
image: quagga2/build
volumes:
- .:/quagga2
command: "npm run build"

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Some files were not shown because too many files have changed in this diff Show More